Duin 0.0.1
Game Engine
Loading...
Searching...
No Matches
imgui.cpp
1// dear imgui, v1.92.0 WIP
2// (main code and documentation)
3
4// Help:
5// - See links below.
6// - Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp. All applications in examples/ are doing that.
7// - Read top of imgui.cpp for more details, links and comments.
8
9// Resources:
10// - FAQ ........................ https://dearimgui.com/faq (in repository as docs/FAQ.md)
11// - Homepage ................... https://github.com/ocornut/imgui
12// - Releases & changelog ....... https://github.com/ocornut/imgui/releases
13// - Gallery .................... https://github.com/ocornut/imgui/issues?q=label%3Agallery (please post your
14// screenshots/video there!)
15// - Wiki ....................... https://github.com/ocornut/imgui/wiki (lots of good stuff there)
16// - Getting Started https://github.com/ocornut/imgui/wiki/Getting-Started (how to integrate in an existing
17// app by adding ~25 lines of code)
18// - Third-party Extensions https://github.com/ocornut/imgui/wiki/Useful-Extensions (ImPlot & many more)
19// - Bindings/Backends https://github.com/ocornut/imgui/wiki/Bindings (language bindings, backends for
20// various tech/engines)
21// - Glossary https://github.com/ocornut/imgui/wiki/Glossary
22// - Debug Tools https://github.com/ocornut/imgui/wiki/Debug-Tools
23// - Software using Dear ImGui https://github.com/ocornut/imgui/wiki/Software-using-dear-imgui
24// - Issues & support ........... https://github.com/ocornut/imgui/issues
25// - Test Engine & Automation ... https://github.com/ocornut/imgui_test_engine (test suite, test engine to automate your
26// apps)
27
28// For first-time users having issues compiling/linking/running/loading fonts:
29// please post in https://github.com/ocornut/imgui/discussions if you cannot find a solution in resources above.
30// Everything else should be asked in 'Issues'! We are building a database of cross-linked knowledge there.
31
32// Copyright (c) 2014-2025 Omar Cornut
33// Developed by Omar Cornut and every direct or indirect contributors to the GitHub.
34// See LICENSE.txt for copyright and licensing details (standard MIT License).
35// This library is free but needs your support to sustain development and maintenance.
36// Businesses: you can support continued development via B2B invoiced technical support, maintenance and sponsoring
37// contracts. PLEASE reach out at omar AT dearimgui DOT com. See https://github.com/ocornut/imgui/wiki/Funding
38// Businesses: you can also purchase licenses for the Dear ImGui Automation/Test Engine.
39
40// It is recommended that you don't modify imgui.cpp! It will become difficult for you to update the library.
41// Note that 'ImGui::' being a namespace, you can add functions into the namespace from your own source files, without
42// modifying imgui.h or imgui.cpp. You may include imgui_internal.h to access internal data structures, but it doesn't
43// come with any guarantee of forward compatibility. Discussing your changes on the GitHub Issue Tracker may lead you
44// to a better solution or official support for them.
45
46/*
47
48Index of this file:
49
50DOCUMENTATION
51
52- MISSION STATEMENT
53- CONTROLS GUIDE
54- PROGRAMMER GUIDE
55 - READ FIRST
56 - HOW TO UPDATE TO A NEWER VERSION OF DEAR IMGUI
57 - GETTING STARTED WITH INTEGRATING DEAR IMGUI IN YOUR CODE/ENGINE
58 - HOW A SIMPLE APPLICATION MAY LOOK LIKE
59 - HOW A SIMPLE RENDERING FUNCTION MAY LOOK LIKE
60- API BREAKING CHANGES (read me when you update!)
61- FREQUENTLY ASKED QUESTIONS (FAQ)
62 - Read all answers online: https://www.dearimgui.com/faq, or in docs/FAQ.md (with a Markdown viewer)
63
64CODE
65(search for "[SECTION]" in the code to find them)
66
67// [SECTION] INCLUDES
68// [SECTION] FORWARD DECLARATIONS
69// [SECTION] CONTEXT AND MEMORY ALLOCATORS
70// [SECTION] USER FACING STRUCTURES (ImGuiStyle, ImGuiIO, ImGuiPlatformIO)
71// [SECTION] MISC HELPERS/UTILITIES (Geometry functions)
72// [SECTION] MISC HELPERS/UTILITIES (String, Format, Hash functions)
73// [SECTION] MISC HELPERS/UTILITIES (File functions)
74// [SECTION] MISC HELPERS/UTILITIES (ImText* functions)
75// [SECTION] MISC HELPERS/UTILITIES (Color functions)
76// [SECTION] ImGuiStorage
77// [SECTION] ImGuiTextFilter
78// [SECTION] ImGuiTextBuffer, ImGuiTextIndex
79// [SECTION] ImGuiListClipper
80// [SECTION] STYLING
81// [SECTION] RENDER HELPERS
82// [SECTION] INITIALIZATION, SHUTDOWN
83// [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!)
84// [SECTION] ID STACK
85// [SECTION] INPUTS
86// [SECTION] ERROR CHECKING, STATE RECOVERY
87// [SECTION] ITEM SUBMISSION
88// [SECTION] LAYOUT
89// [SECTION] SCROLLING
90// [SECTION] TOOLTIPS
91// [SECTION] POPUPS
92// [SECTION] WINDOW FOCUS
93// [SECTION] KEYBOARD/GAMEPAD NAVIGATION
94// [SECTION] DRAG AND DROP
95// [SECTION] LOGGING/CAPTURING
96// [SECTION] SETTINGS
97// [SECTION] LOCALIZATION
98// [SECTION] VIEWPORTS, PLATFORM WINDOWS
99// [SECTION] DOCKING
100// [SECTION] PLATFORM DEPENDENT HELPERS
101// [SECTION] METRICS/DEBUGGER WINDOW
102// [SECTION] DEBUG LOG WINDOW
103// [SECTION] OTHER DEBUG TOOLS (ITEM PICKER, ID STACK TOOL)
104
105*/
106
107//-----------------------------------------------------------------------------
108// DOCUMENTATION
109//-----------------------------------------------------------------------------
110
111/*
112
113 MISSION STATEMENT
114 =================
115
116 - Easy to use to create code-driven and data-driven tools.
117 - Easy to use to create ad hoc short-lived tools and long-lived, more elaborate tools.
118 - Easy to hack and improve.
119 - Minimize setup and maintenance.
120 - Minimize state storage on user side.
121 - Minimize state synchronization.
122 - Portable, minimize dependencies, run on target (consoles, phones, etc.).
123 - Efficient runtime and memory consumption.
124
125 Designed primarily for developers and content-creators, not the typical end-user!
126 Some of the current weaknesses (which we aim to address in the future) includes:
127
128 - Doesn't look fancy.
129 - Limited layout features, intricate layouts are typically crafted in code.
130
131
132 CONTROLS GUIDE
133 ==============
134
135 - MOUSE CONTROLS
136 - Mouse wheel: Scroll vertically.
137 - SHIFT+Mouse wheel: Scroll horizontally.
138 - Click [X]: Close a window, available when 'bool* p_open' is passed to ImGui::Begin().
139 - Click ^, Double-Click title: Collapse window.
140 - Drag on corner/border: Resize window (double-click to auto fit window to its contents).
141 - Drag on any empty space: Move window (unless io.ConfigWindowsMoveFromTitleBarOnly = true).
142 - Left-click outside popup: Close popup stack (right-click over underlying popup: Partially close popup stack).
143
144 - TEXT EDITOR
145 - Hold SHIFT or Drag Mouse: Select text.
146 - CTRL+Left/Right: Word jump.
147 - CTRL+Shift+Left/Right: Select words.
148 - CTRL+A or Double-Click: Select All.
149 - CTRL+X, CTRL+C, CTRL+V: Use OS clipboard.
150 - CTRL+Z Undo.
151 - CTRL+Y or CTRL+Shift+Z: Redo.
152 - ESCAPE: Revert text to its original value.
153 - On OSX, controls are automatically adjusted to match standard OSX text editing 2ts and behaviors.
154
155 - KEYBOARD CONTROLS
156 - Basic:
157 - Tab, SHIFT+Tab Cycle through text editable fields.
158 - CTRL+Tab, CTRL+Shift+Tab Cycle through windows.
159 - CTRL+Click Input text into a Slider or Drag widget.
160 - Extended features with `io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard`:
161 - Tab, SHIFT+Tab: Cycle through every items.
162 - Arrow keys Move through items using directional navigation. Tweak value.
163 - Arrow keys + Alt, Shift Tweak slower, tweak faster (when using arrow keys).
164 - Enter Activate item (prefer text input when possible).
165 - Space Activate item (prefer tweaking with arrows when possible).
166 - Escape Deactivate item, leave child window, close popup.
167 - Page Up, Page Down Previous page, next page.
168 - Home, End Scroll to top, scroll to bottom.
169 - Alt Toggle between scrolling layer and menu layer.
170 - CTRL+Tab then Ctrl+Arrows Move window. Hold SHIFT to resize instead of moving.
171 - Output when ImGuiConfigFlags_NavEnableKeyboard set,
172 - io.WantCaptureKeyboard flag is set when keyboard is claimed.
173 - io.NavActive: true when a window is focused and it doesn't have the ImGuiWindowFlags_NoNavInputs flag set.
174 - io.NavVisible: true when the navigation cursor is visible (usually goes to back false when mouse is used).
175
176 - GAMEPAD CONTROLS
177 - Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
178 - Particularly useful to use Dear ImGui on a console system (e.g. PlayStation, Switch, Xbox) without a mouse!
179 - Download controller mapping PNG/PSD at http://dearimgui.com/controls_sheets
180 - Backend support: backend needs to:
181 - Set 'io.BackendFlags |= ImGuiBackendFlags_HasGamepad' + call io.AddKeyEvent/AddKeyAnalogEvent() with
182ImGuiKey_Gamepad_XXX keys.
183 - For analog values (0.0f to 1.0f), backend is responsible to handling a dead-zone and rescaling inputs
184accordingly. Backend code will probably need to transform your raw inputs (such as e.g. remapping your 0.2..0.9 raw
185input range to 0.0..1.0 imgui range, etc.).
186 - If you need to share inputs between your game and the Dear ImGui interface, the easiest approach is to go
187all-or-nothing, with a buttons combo to toggle the target. Please reach out if you think the game vs navigation input
188sharing could be improved.
189
190 - REMOTE INPUTS SHARING & MOUSE EMULATION
191 - PS4/PS5 users: Consider emulating a mouse cursor with DualShock touch pad or a spare analog stick as a
192mouse-emulation fallback.
193 - Consoles/Tablet/Phone users: Consider using a Synergy 1.x server (on your PC) + run
194examples/libs/synergy/uSynergy.c (on your console/tablet/phone app) in order to share your PC mouse/keyboard.
195 - See https://github.com/ocornut/imgui/wiki/Useful-Extensions#remoting for other remoting solutions.
196 - On a TV/console system where readability may be lower or mouse inputs may be awkward, you may want to set the
197io.ConfigNavMoveSetMousePos flag. Enabling io.ConfigNavMoveSetMousePos + ImGuiBackendFlags_HasSetMousePos instructs Dear
198ImGui to move your mouse cursor along with navigation movements. When enabled, the NewFrame() function may alter
199'io.MousePos' and set 'io.WantSetMousePos' to notify you that it wants the mouse cursor to be moved. When that happens
200your backend NEEDS to move the OS or underlying mouse cursor on the next frame. Some of the backends in examples/ do
201that. (If you set the NavEnableSetMousePos flag but don't honor 'io.WantSetMousePos' properly, Dear ImGui will misbehave
202as it will see your mouse moving back & forth!) (In a setup when you may not have easy control over the mouse cursor,
203e.g. uSynergy.c doesn't expose moving remote mouse cursor, you may want to set a boolean to ignore your other external
204mouse positions until the external source is moved again.)
205
206
207 PROGRAMMER GUIDE
208 ================
209
210 READ FIRST
211 ----------
212 - Remember to check the wonderful Wiki (https://github.com/ocornut/imgui/wiki)
213 - Your code creates the UI every frame of your application loop, if your code doesn't run the UI is gone!
214 The UI can be highly dynamic, there are no construction or destruction steps, less superfluous
215 data retention on your side, less state duplication, less state synchronization, fewer bugs.
216 - Call and read ImGui::ShowDemoWindow() for demo code demonstrating most features.
217 Or browse https://pthom.github.io/imgui_manual_online/manual/imgui_manual.html for interactive web version.
218 - The library is designed to be built from sources. Avoid pre-compiled binaries and packaged versions. See imconfig.h
219to configure your build.
220 - Dear ImGui is an implementation of the IMGUI paradigm (immediate-mode graphical user interface, a term coined by
221Casey Muratori). You can learn about IMGUI principles at http://www.johno.se/book/imgui.html, http://mollyrocket.com/861
222& more links in Wiki.
223 - Dear ImGui is a "single pass" rasterizing implementation of the IMGUI paradigm, aimed at ease of use and
224high-performances. For every application frame, your UI code will be called only once. This is in contrast to e.g.
225Unity's implementation of an IMGUI, where the UI code is called multiple times ("multiple passes") from a single entry
226point. There are pros and cons to both approaches.
227 - Our origin is on the top-left. In axis aligned bounding boxes, Min = top-left, Max = bottom-right.
228 - Please make sure you have asserts enabled (IM_ASSERT redirects to assert() by default, but can be redirected).
229 If you get an assert, read the messages and comments around the assert.
230 - This codebase aims to be highly optimized:
231 - A typical idle frame should never call malloc/free.
232 - We rely on a maximum of constant-time or O(N) algorithms. Limiting searches/scans as much as possible.
233 - We put particular energy in making sure performances are decent with typical "Debug" build settings as well.
234 Which mean we tend to avoid over-relying on "zero-cost abstraction" as they aren't zero-cost at all.
235 - This codebase aims to be both highly opinionated and highly flexible:
236 - This code works because of the things it choose to solve or not solve.
237 - C++: this is a pragmatic C-ish codebase: we don't use fancy C++ features, we don't include C++ headers,
238 and ImGui:: is a namespace. We rarely use member functions (and when we did, I am mostly regretting it now).
239 This is to increase compatibility, increase maintainability and facilitate use from other languages.
240 - C++: ImVec2/ImVec4 do not expose math operators by default, because it is expected that you use your own math
241types. See FAQ "How can I use my own math types instead of ImVec2/ImVec4?" for details about setting up imconfig.h for
242that. We can can optionally export math operators for ImVec2/ImVec4 using IMGUI_DEFINE_MATH_OPERATORS, which we use
243internally.
244 - C++: pay attention that ImVector<> manipulates plain-old-data and does not honor construction/destruction
245 (so don't use ImVector in your code or at our own risk!).
246 - Building: We don't use nor mandate a build system for the main library.
247 This is in an effort to ensure that it works in the real world aka with any esoteric build setup.
248 This is also because providing a build system for the main library would be of little-value.
249 The build problems are almost never coming from the main library but from specific backends.
250
251
252 HOW TO UPDATE TO A NEWER VERSION OF DEAR IMGUI
253 ----------------------------------------------
254 - Update submodule or copy/overwrite every file.
255 - About imconfig.h:
256 - You may modify your copy of imconfig.h, in this case don't overwrite it.
257 - or you may locally branch to modify imconfig.h and merge/rebase latest.
258 - or you may '#define IMGUI_USER_CONFIG "my_config_file.h"' globally from your build system to
259 specify a custom path for your imconfig.h file and instead not have to modify the default one.
260
261 - Overwrite all the sources files except for imconfig.h (if you have modified your copy of imconfig.h)
262 - Or maintain your own branch where you have imconfig.h modified as a top-most commit which you can regularly rebase
263over "master".
264 - You can also use '#define IMGUI_USER_CONFIG "my_config_file.h" to redirect configuration to your own file.
265 - Read the "API BREAKING CHANGES" section (below). This is where we list occasional API breaking changes.
266 If a function/type has been renamed / or marked obsolete, try to fix the name in your code before it is permanently
267removed from the public API. If you have a problem with a missing function/symbols, search for its name in the code,
268there will likely be a comment about it. Please report any issue to the GitHub page!
269 - To find out usage of old API, you can add '#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS' in your configuration file.
270 - Try to keep your copy of Dear ImGui reasonably up to date!
271
272
273 GETTING STARTED WITH INTEGRATING DEAR IMGUI IN YOUR CODE/ENGINE
274 ---------------------------------------------------------------
275 - See https://github.com/ocornut/imgui/wiki/Getting-Started.
276 - Run and study the examples and demo in imgui_demo.cpp to get acquainted with the library.
277 - In the majority of cases you should be able to use unmodified backends files available in the backends/ folder.
278 - Add the Dear ImGui source files + selected backend source files to your projects or using your preferred build
279system. It is recommended you build and statically link the .cpp files as part of your project and NOT as a shared
280library (DLL).
281 - You can later customize the imconfig.h file to tweak some compile-time behavior, such as integrating Dear ImGui types
282with your own maths types.
283 - When using Dear ImGui, your programming IDE is your friend: follow the declaration of variables, functions and types
284to find comments about them.
285 - Dear ImGui never touches or knows about your GPU state. The only function that knows about GPU is the draw function
286that you provide. Effectively it means you can create widgets at any time in your code, regardless of considerations of
287being in "update" vs "render" phases of your own application. All rendering information is stored into command-lists
288that you will retrieve after calling ImGui::Render().
289 - Refer to the backends and demo applications in the examples/ folder for instruction on how to setup your code.
290 - If you are running over a standard OS with a common graphics API, you should be able to use unmodified imgui_impl_***
291files from the examples/ folder.
292
293
294 HOW A SIMPLE APPLICATION MAY LOOK LIKE
295 --------------------------------------
296 EXHIBIT 1: USING THE EXAMPLE BACKENDS (= imgui_impl_XXX.cpp files from the backends/ folder).
297 The sub-folders in examples/ contain examples applications following this structure.
298
299 // Application init: create a dear imgui context, setup some options, load fonts
300 ImGui::CreateContext();
301 ImGuiIO& io = ImGui::GetIO();
302 // TODO: Set optional io.ConfigFlags values, e.g. 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard' to enable
303keyboard controls.
304 // TODO: Fill optional fields of the io structure later.
305 // TODO: Load TTF/OTF fonts if you don't want to use the default font.
306
307 // Initialize helper Platform and Renderer backends (here we are using imgui_impl_win32.cpp and
308imgui_impl_dx11.cpp) ImGui_ImplWin32_Init(hwnd); ImGui_ImplDX11_Init(g_pd3dDevice, g_pd3dDeviceContext);
309
310 // Application main loop
311 while (true)
312 {
313 // Feed inputs to dear imgui, start new frame
314 ImGui_ImplDX11_NewFrame();
315 ImGui_ImplWin32_NewFrame();
316 ImGui::NewFrame();
317
318 // Any application code here
319 ImGui::Text("Hello, world!");
320
321 // Render dear imgui into screen
322 ImGui::Render();
323 ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
324 g_pSwapChain->Present(1, 0);
325 }
326
327 // Shutdown
328 ImGui_ImplDX11_Shutdown();
329 ImGui_ImplWin32_Shutdown();
330 ImGui::DestroyContext();
331
332 EXHIBIT 2: IMPLEMENTING CUSTOM BACKEND / CUSTOM ENGINE
333
334 // Application init: create a dear imgui context, setup some options, load fonts
335 ImGui::CreateContext();
336 ImGuiIO& io = ImGui::GetIO();
337 // TODO: Set optional io.ConfigFlags values, e.g. 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard' to enable
338keyboard controls.
339 // TODO: Fill optional fields of the io structure later.
340 // TODO: Load TTF/OTF fonts if you don't want to use the default font.
341
342 // Build and load the texture atlas into a texture
343 // (In the examples/ app this is usually done within the ImGui_ImplXXX_Init() function from one of the demo
344Renderer) int width, height; unsigned char* pixels = nullptr; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
345
346 // At this point you've got the texture data and you need to upload that to your graphic system:
347 // After we have created the texture, store its pointer/identifier (_in whichever format your engine uses_) in
348'io.Fonts->TexID'.
349 // This will be passed back to your via the renderer. Basically ImTextureID == void*. Read FAQ for details about
350ImTextureID. MyTexture* texture = MyEngine::CreateTextureFromMemoryPixels(pixels, width, height, TEXTURE_TYPE_RGBA32)
351 io.Fonts->SetTexID((void*)texture);
352
353 // Application main loop
354 while (true)
355 {
356 // Setup low-level inputs, e.g. on Win32: calling GetKeyboardState(), or write to those fields from your Windows
357message handlers, etc.
358 // (In the examples/ app this is usually done within the ImGui_ImplXXX_NewFrame() function from one of the demo
359Platform Backends) io.DeltaTime = 1.0f/60.0f; // set the time elapsed since the previous frame (in seconds)
360 io.DisplaySize.x = 1920.0f; // set the current display width
361 io.DisplaySize.y = 1280.0f; // set the current display height here
362 io.AddMousePosEvent(mouse_x, mouse_y); // update mouse position
363 io.AddMouseButtonEvent(0, mouse_b[0]); // update mouse button states
364 io.AddMouseButtonEvent(1, mouse_b[1]); // update mouse button states
365
366 // Call NewFrame(), after this point you can use ImGui::* functions anytime
367 // (So you want to try calling NewFrame() as early as you can in your main loop to be able to use Dear ImGui
368everywhere) ImGui::NewFrame();
369
370 // Most of your application code here
371 ImGui::Text("Hello, world!");
372 MyGameUpdate(); // may use any Dear ImGui functions, e.g. ImGui::Begin("My window"); ImGui::Text("Hello,
373world!"); ImGui::End(); MyGameRender(); // may use any Dear ImGui functions as well!
374
375 // Render dear imgui, swap buffers
376 // (You want to try calling EndFrame/Render as late as you can, to be able to use Dear ImGui in your own game
377rendering code) ImGui::EndFrame(); ImGui::Render(); ImDrawData* draw_data = ImGui::GetDrawData();
378 MyImGuiRenderFunction(draw_data);
379 SwapBuffers();
380 }
381
382 // Shutdown
383 ImGui::DestroyContext();
384
385 To decide whether to dispatch mouse/keyboard inputs to Dear ImGui to the rest of your application,
386 you should read the 'io.WantCaptureMouse', 'io.WantCaptureKeyboard' and 'io.WantTextInput' flags!
387 Please read the FAQ entry "How can I tell whether to dispatch mouse/keyboard to Dear ImGui or my application?" about
388this.
389
390
391 HOW A SIMPLE RENDERING FUNCTION MAY LOOK LIKE
392 ---------------------------------------------
393 The backends in impl_impl_XXX.cpp files contain many working implementations of a rendering function.
394
395 void MyImGuiRenderFunction(ImDrawData* draw_data)
396 {
397 // TODO: Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled
398 // TODO: Setup texture sampling state: sample with bilinear filtering (NOT point/nearest filtering). Use
399'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines;' to allow point/nearest filtering.
400 // TODO: Setup viewport covering draw_data->DisplayPos to draw_data->DisplayPos + draw_data->DisplaySize
401 // TODO: Setup orthographic projection matrix cover draw_data->DisplayPos to draw_data->DisplayPos +
402draw_data->DisplaySize
403 // TODO: Setup shader: vertex { float2 pos, float2 uv, u32 color }, fragment shader sample color from 1 texture,
404multiply by vertex color. ImVec2 clip_off = draw_data->DisplayPos; for (int n = 0; n < draw_data->CmdListsCount; n++)
405 {
406 const ImDrawList* cmd_list = draw_data->CmdLists[n];
407 const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data; // vertex buffer generated by Dear ImGui
408 const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data; // index buffer generated by Dear ImGui
409 for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
410 {
411 const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
412 if (pcmd->UserCallback)
413 {
414 pcmd->UserCallback(cmd_list, pcmd);
415 }
416 else
417 {
418 // Project scissor/clipping rectangles into framebuffer space
419 ImVec2 clip_min(pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y);
420 ImVec2 clip_max(pcmd->ClipRect.z - clip_off.x, pcmd->ClipRect.w - clip_off.y);
421 if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
422 continue;
423
424 // We are using scissoring to clip some objects. All low-level graphics API should support it.
425 // - If your engine doesn't support scissoring yet, you may ignore this at first. You will get some
426small glitches
427 // (some elements visible outside their bounds) but you can fix that once everything else works!
428 // - Clipping coordinates are provided in imgui coordinates space:
429 // - For a given viewport, draw_data->DisplayPos == viewport->Pos and draw_data->DisplaySize ==
430viewport->Size
431 // - In a single viewport application, draw_data->DisplayPos == (0,0) and draw_data->DisplaySize ==
432io.DisplaySize, but always use GetMainViewport()->Pos/Size instead of hardcoding those values.
433 // - In the interest of supporting multi-viewport applications (see 'docking' branch on github),
434 // always subtract draw_data->DisplayPos from clipping bounds to convert them to your viewport
435space.
436 // - Note that pcmd->ClipRect contains Min+Max bounds. Some graphics API may use Min+Max, other may use
437Min+Size (size being Max-Min) MyEngineSetScissor(clip_min.x, clip_min.y, clip_max.x, clip_max.y);
438
439 // The texture for the draw call is specified by pcmd->GetTexID().
440 // The vast majority of draw calls will use the Dear ImGui texture atlas, which value you have set
441yourself during initialization. MyEngineBindTexture((MyTexture*)pcmd->GetTexID());
442
443 // Render 'pcmd->ElemCount/3' indexed triangles.
444 // By default the indices ImDrawIdx are 16-bit, you can change them to 32-bit in imconfig.h if your
445engine doesn't support 16-bit indices. MyEngineDrawIndexedTriangles(pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ?
446GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer + pcmd->IdxOffset, vtx_buffer, pcmd->VtxOffset);
447 }
448 }
449 }
450 }
451
452
453 API BREAKING CHANGES
454 ====================
455
456 Occasionally introducing changes that are breaking the API. We try to make the breakage minor and easy to fix.
457 Below is a change-log of API breaking changes only. If you are using one of the functions listed, expect to have to fix
458some code. When you are not sure about an old symbol or function name, try using the Search/Find function of your IDE to
459look for comments or references in all imgui files. You can read releases logs https://github.com/ocornut/imgui/releases
460for more details.
461
462(Docking/Viewport Branch)
463 - 2025/XX/XX (1.XXXX) - when multi-viewports are enabled, all positions will be in your natural OS coordinates space.
464It means that:
465 - reference to hard-coded positions such as in SetNextWindowPos(ImVec2(0,0)) are probably not
466what you want anymore. you may use GetMainViewport()->Pos to offset hard-coded positions, e.g.
467SetNextWindowPos(GetMainViewport()->Pos)
468 - likewise io.MousePos and GetMousePos() will use OS coordinates.
469 If you query mouse positions to interact with non-imgui coordinates you will need to offset
470them, e.g. subtract GetWindowViewport()->Pos.
471
472 - 2025/03/05 (1.91.9) - BeginMenu(): Internals: reworked mangling of menu windows to use "###Menu_00" etc. instead of
473"##Menu_00", allowing them to also store the menu name before it. This shouldn't affect code unless directly accessing
474menu window from their mangled name.
475 - 2025/02/27 (1.91.9) - Image(): removed 'tint_col' and 'border_col' parameter from Image() function. Added
476ImageWithBg() replacement. (#8131, #8238)
477 - old: void Image (ImTextureID tex_id, ImVec2 image_size, ImVec2 uv0 = (0,0), ImVec2
478uv1 = (1,1), ImVec4 tint_col = (1,1,1,1), ImVec4 border_col = (0,0,0,0));
479 - new: void Image (ImTextureID tex_id, ImVec2 image_size, ImVec2 uv0 = (0,0), ImVec2
480uv1 = (1,1));
481 - new: void ImageWithBg(ImTextureID tex_id, ImVec2 image_size, ImVec2 uv0 = (0,0), ImVec2
482uv1 = (1,1), ImVec4 bg_col = (0,0,0,0), ImVec4 tint_col = (1,1,1,1));
483 - TL;DR: 'border_col' had misleading side-effect on layout, 'bg_col' was missing, parameter
484order couldn't be consistent with ImageButton().
485 - new behavior always use ImGuiCol_Border color + style.ImageBorderSize /
486ImGuiStyleVar_ImageBorderSize.
487 - old behavior altered border size (and therefore layout) based on border color's alpha,
488which caused variety of problems + old behavior a fixed 1.0f for border size which was not tweakable.
489 - kept legacy signature (will obsolete), which mimics the old behavior, but uses Max(1.0f,
490style.ImageBorderSize) when border_col is specified.
491 - added ImageWithBg() function which has both 'bg_col' (which was missing) and 'tint_col'.
492It was impossible to add 'bg_col' to Image() with a parameter order consistent with other functions, so we decided to
493remove 'tint_col' and introduce ImageWithBg().
494 - 2025/02/25 (1.91.9) - internals: fonts: ImFontAtlas::ConfigData[] has been renamed to ImFontAtlas::Sources[].
495ImFont::ConfigData[], ConfigDataCount has been renamed to Sources[], SourcesCount.
496 - 2025/02/06 (1.91.9) - renamed ImFontConfig::GlyphExtraSpacing.x to ImFontConfig::GlyphExtraAdvanceX.
497 - 2025/01/22 (1.91.8) - removed ImGuiColorEditFlags_AlphaPreview (made value 0): it is now the default behavior.
498 prior to 1.91.8: alpha was made opaque in the preview by default _unless_ using
499ImGuiColorEditFlags_AlphaPreview. We now display the preview as transparent by default. You can use
500ImGuiColorEditFlags_AlphaOpaque to use old behavior. the new flags (ImGuiColorEditFlags_AlphaOpaque,
501ImGuiColorEditFlags_AlphaNoBg + existing ImGuiColorEditFlags_AlphaPreviewHalf) may be combined better and allow finer
502controls:
503 - 2025/01/14 (1.91.7) - renamed ImGuiTreeNodeFlags_SpanTextWidth to ImGuiTreeNodeFlags_SpanLabelWidth for consistency
504with other names. Kept redirection enum (will obsolete). (#6937)
505 - 2024/11/27 (1.91.6) - changed CRC32 table from CRC32-adler to CRC32c polynomial in order to be compatible with the
506result of SSE 4.2 instructions. As a result, old .ini data may be partially lost (docking and tables information
507particularly). Because some users have crafted and storing .ini data as a way to workaround limitations of the docking
508API, we are providing a '#define IMGUI_USE_LEGACY_CRC32_ADLER' compile-time option to keep using old CRC32 tables if you
509cannot afford invalidating old .ini data.
510 - 2024/11/06 (1.91.5) - commented/obsoleted out pre-1.87 IO system (equivalent to using IMGUI_DISABLE_OBSOLETE_KEYIO or
511IMGUI_DISABLE_OBSOLETE_FUNCTIONS before)
512 - io.KeyMap[] and io.KeysDown[] are removed (obsoleted February 2022).
513 - io.NavInputs[] and ImGuiNavInput are removed (obsoleted July 2022).
514 - pre-1.87 backends are not supported:
515 - backends need to call io.AddKeyEvent(), io.AddMouseEvent() instead of writing to
516io.KeysDown[], io.MouseDown[] fields.
517 - backends need to call io.AddKeyAnalogEvent() for gamepad values instead of writing to
518io.NavInputs[] fields.
519 - for more reference:
520 - read 1.87 and 1.88 part of this section or read Changelog for 1.87 and 1.88.
521 - read https://github.com/ocornut/imgui/issues/4921
522 - if you have trouble updating a very old codebase using legacy backend-specific key codes:
523consider updating to 1.91.4 first, then #define IMGUI_DISABLE_OBSOLETE_KEYIO, then update to latest.
524 - obsoleted ImGuiKey_COUNT (it is unusually error-prone/misleading since valid keys don't start
525at 0). probably use ImGuiKey_NamedKey_BEGIN/ImGuiKey_NamedKey_END?
526 - fonts: removed const qualifiers from most font functions in prevision for upcoming font
527improvements.
528 - 2024/10/18 (1.91.4) - renamed ImGuiCol_NavHighlight to ImGuiCol_NavCursor (for consistency with newly exposed and
529reworked features). Kept inline redirection enum (will obsolete).
530 - 2024/10/14 (1.91.4) - moved ImGuiConfigFlags_NavEnableSetMousePos to standalone io.ConfigNavMoveSetMousePos bool.
531 moved ImGuiConfigFlags_NavNoCaptureKeyboard to standalone io.ConfigNavCaptureKeyboard bool
532(note the inverted value!). kept legacy names (will obsolete) + code that copies settings once the first time.
533Dynamically changing the old value won't work. Switch to using the new value!
534 - 2024/10/10 (1.91.4) - the typedef for ImTextureID now defaults to ImU64 instead of void*. (#1641)
535 this removes the requirement to redefine it for backends which are e.g. storing descriptor sets
536or other 64-bits structures when building on 32-bits archs. It therefore simplify various building scripts/helpers. you
537may have compile-time issues if you were casting to 'void*' instead of 'ImTextureID' when passing your types to
538functions taking ImTextureID values, e.g. ImGui::Image(). in doubt it is almost always better to do an intermediate
539intptr_t cast, since it allows casting any pointer/integer type without warning:
540 - May warn: ImGui::Image((void*)MyTextureData, ...);
541 - May warn: ImGui::Image((void*)(intptr_t)MyTextureData, ...);
542 - Won't warn: ImGui::Image((ImTextureID)(intptr_t)MyTextureData), ...);
543 - note that you can always define ImTextureID to be your own high-level structures (with
544dedicated constructors) if you like.
545 - 2024/10/03 (1.91.3) - drags: treat v_min==v_max as a valid clamping range when != 0.0f. Zero is a still special value
546due to legacy reasons, unless using ImGuiSliderFlags_ClampZeroRange. (#7968, #3361, #76)
547 - drags: extended behavior of ImGuiSliderFlags_AlwaysClamp to include _ClampZeroRange. It
548considers v_min==v_max==0.0f as a valid clamping range (aka edits not allowed). although unlikely, it you wish to only
549clamp on text input but want v_min==v_max==0.0f to mean unclamped drags, you can use _ClampOnInput instead of
550_AlwaysClamp. (#7968, #3361, #76)
551 - 2024/09/10 (1.91.2) - internals: using multiple overlaid ButtonBehavior() with same ID will now have
552io.ConfigDebugHighlightIdConflicts=true feature emit a warning. (#8030) it was one of the rare case where using same ID
553is legal. workarounds: (1) use single ButtonBehavior() call with multiple _MouseButton flags, or (2) surround the calls
554with PushItemFlag(ImGuiItemFlags_AllowDuplicateId, true); ... PopItemFlag()
555 - 2024/08/23 (1.91.1) - renamed ImGuiChildFlags_Border to ImGuiChildFlags_Borders for consistency. kept inline
556redirection flag.
557 - 2024/08/22 (1.91.1) - moved some functions from ImGuiIO to ImGuiPlatformIO structure:
558 - io.GetClipboardTextFn -> platform_io.Platform_GetClipboardTextFn + changed 'void*
559user_data' to 'ImGuiContext* ctx'. Pull your user data from platform_io.ClipboardUserData.
560 - io.SetClipboardTextFn -> platform_io.Platform_SetClipboardTextFn + same as above
561line.
562 - io.PlatformOpenInShellFn -> platform_io.Platform_OpenInShellFn (#7660)
563 - io.PlatformSetImeDataFn -> platform_io.Platform_SetImeDataFn
564 - io.PlatformLocaleDecimalPoint -> platform_io.Platform_LocaleDecimalPoint (#7389, #6719,
565#2278)
566 - access those via GetPlatformIO() instead of GetIO().
567 some were introduced very recently and often automatically setup by core library and backends,
568so for those we are exceptionally not maintaining a legacy redirection symbol.
569 - commented the old ImageButton() signature obsoleted in 1.89 (~August 2022). As a reminder:
570 - old ImageButton() before 1.89 used ImTextureId as item id (created issue with e.g.
571multiple buttons in same scope, transient texture id values, opaque computation of ID)
572 - new ImageButton() since 1.89 requires an explicit 'const char* str_id'
573 - old ImageButton() before 1.89 had frame_padding' override argument.
574 - new ImageButton() since 1.89 always use style.FramePadding, which you can freely override
575with PushStyleVar()/PopStyleVar().
576 - 2024/07/25 (1.91.0) - obsoleted GetContentRegionMax(), GetWindowContentRegionMin() and GetWindowContentRegionMax().
577(see #7838 on GitHub for more info) you should never need those functions. you can do everything with
578GetCursorScreenPos() and GetContentRegionAvail() in a more simple way.
579 - instead of: GetWindowContentRegionMax().x - GetCursorPos().x
580 - you can use: GetContentRegionAvail().x
581 - instead of: GetWindowContentRegionMax().x + GetWindowPos().x
582 - you can use: GetCursorScreenPos().x + GetContentRegionAvail().x // when called from left
583edge of window
584 - instead of: GetContentRegionMax()
585 - you can use: GetContentRegionAvail() + GetCursorScreenPos() - GetWindowPos() // right edge
586in local coordinates
587 - instead of: GetWindowContentRegionMax().x - GetWindowContentRegionMin().x
588 - you can use: GetContentRegionAvail() // when called from left edge of window
589 - 2024/07/15 (1.91.0) - renamed ImGuiSelectableFlags_DontClosePopups to ImGuiSelectableFlags_NoAutoClosePopups. (#1379,
590#1468, #2200, #4936, #5216, #7302, #7573) (internals: also renamed ImGuiItemFlags_SelectableDontClosePopup into
591ImGuiItemFlags_AutoClosePopups with inverted behaviors)
592 - 2024/07/15 (1.91.0) - obsoleted PushButtonRepeat()/PopButtonRepeat() in favor of using new
593PushItemFlag(ImGuiItemFlags_ButtonRepeat, ...)/PopItemFlag().
594 - 2024/07/02 (1.91.0) - commented out obsolete ImGuiModFlags (renamed to ImGuiKeyChord in 1.89). (#4921, #456)
595 - commented out obsolete ImGuiModFlags_XXX values (renamed to ImGuiMod_XXX in 1.89). (#4921,
596#456)
597 - ImGuiModFlags_Ctrl -> ImGuiMod_Ctrl, ImGuiModFlags_Shift -> ImGuiMod_Shift etc.
598 - 2024/07/02 (1.91.0) - IO, IME: renamed platform IME hook and added explicit context for consistency and
599future-proofness.
600 - old: io.SetPlatformImeDataFn(ImGuiViewport* viewport, ImGuiPlatformImeData* data);
601 - new: io.PlatformSetImeDataFn(ImGuiContext* ctx, ImGuiViewport* viewport,
602ImGuiPlatformImeData* data);
603 - 2024/06/21 (1.90.9) - BeginChild: added ImGuiChildFlags_NavFlattened as a replacement for the window flag
604ImGuiWindowFlags_NavFlattened: the feature only ever made sense for BeginChild() anyhow.
605 - old: BeginChild("Name", size, 0, ImGuiWindowFlags_NavFlattened);
606 - new: BeginChild("Name", size, ImGuiChildFlags_NavFlattened, 0);
607 - 2024/06/21 (1.90.9) - io: ClearInputKeys() (first exposed in 1.89.8) doesn't clear mouse data, newly added
608ClearInputMouse() does.
609 - 2024/06/20 (1.90.9) - renamed ImGuiDragDropFlags_SourceAutoExpirePayload to ImGuiDragDropFlags_PayloadAutoExpire.
610 - 2024/06/18 (1.90.9) - style: renamed ImGuiCol_TabActive -> ImGuiCol_TabSelected, ImGuiCol_TabUnfocused ->
611ImGuiCol_TabDimmed, ImGuiCol_TabUnfocusedActive -> ImGuiCol_TabDimmedSelected.
612 - 2024/06/10 (1.90.9) - removed old nested structure: renaming ImGuiStorage::ImGuiStoragePair type to ImGuiStoragePair
613(simpler for many languages).
614 - 2024/06/06 (1.90.8) - reordered ImGuiInputTextFlags values. This should not be breaking unless you are using
615generated headers that have values not matching the main library.
616 - 2024/06/06 (1.90.8) - removed 'ImGuiButtonFlags_MouseButtonDefault_ = ImGuiButtonFlags_MouseButtonLeft', was mostly
617unused and misleading.
618 - 2024/05/27 (1.90.7) - commented out obsolete symbols marked obsolete in 1.88 (May 2022):
619 - old: CaptureKeyboardFromApp(bool)
620 - new: SetNextFrameWantCaptureKeyboard(bool)
621 - old: CaptureMouseFromApp(bool)
622 - new: SetNextFrameWantCaptureMouse(bool)
623 - 2024/05/22 (1.90.7) - inputs (internals): renamed ImGuiKeyOwner_None to ImGuiKeyOwner_NoOwner, to make use more
624explicit and reduce confusion with the default it is a non-zero value and cannot be the default value (never made
625public, but disclosing as I expect a few users caught on owner-aware inputs).
626 - inputs (internals): renamed ImGuiInputFlags_RouteGlobalLow -> ImGuiInputFlags_RouteGlobal,
627ImGuiInputFlags_RouteGlobal -> ImGuiInputFlags_RouteGlobalOverFocused, ImGuiInputFlags_RouteGlobalHigh ->
628ImGuiInputFlags_RouteGlobalHighest.
629 - inputs (internals): Shortcut(), SetShortcutRouting(): swapped last two parameters order in
630function signatures:
631 - old: Shortcut(ImGuiKeyChord key_chord, ImGuiID owner_id = 0, ImGuiInputFlags flags = 0);
632 - new: Shortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags = 0, ImGuiID owner_id = 0);
633 - inputs (internals): owner-aware versions of IsKeyPressed(), IsKeyChordPressed(),
634IsMouseClicked(): swapped last two parameters order in function signatures.
635 - old: IsKeyPressed(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags = 0);
636 - new: IsKeyPressed(ImGuiKey key, ImGuiInputFlags flags, ImGuiID owner_id = 0);
637 - old: IsMouseClicked(ImGuiMouseButton button, ImGuiID owner_id, ImGuiInputFlags flags = 0);
638 - new: IsMouseClicked(ImGuiMouseButton button, ImGuiInputFlags flags, ImGuiID owner_id = 0);
639 for various reasons those changes makes sense. They are being made because making some of those
640API public. only past users of imgui_internal.h with the extra parameters will be affected. Added asserts for valid
641flags in various functions to detect _some_ misuses, BUT NOT ALL.
642 - 2024/05/21 (1.90.7) - docking: changed signature of DockSpaceOverViewport() to add explicit dockspace id if desired.
643pass 0 to use old behavior. (#7611)
644 - old: DockSpaceOverViewport(const ImGuiViewport* viewport = NULL, ImGuiDockNodeFlags flags =
6450, ...);
646 - new: DockSpaceOverViewport(ImGuiID dockspace_id = 0, const ImGuiViewport* viewport = NULL,
647ImGuiDockNodeFlags flags = 0, ...);
648 - 2024/05/16 (1.90.7) - inputs: on macOS X, Cmd and Ctrl keys are now automatically swapped by io.AddKeyEvent() as this
649naturally align with how macOS X uses those keys.
650 - it shouldn't really affect you unless you had custom shortcut swapping in place for macOS X
651apps.
652 - removed ImGuiMod_Shortcut which was previously dynamically remapping to Ctrl or Cmd/Super.
653It is now unnecessary to specific cross-platform idiomatic shortcuts. (#2343, #4084, #5923, #456)
654 - 2024/05/14 (1.90.7) - backends: SDL_Renderer2 and SDL_Renderer3 backend now take a SDL_Renderer* in their
655RenderDrawData() functions.
656 - 2024/04/18 (1.90.6) - TreeNode: Fixed a layout inconsistency when using an empty/hidden label followed by a
657SameLine() call. (#7505, #282)
658 - old: TreeNode("##Hidden"); SameLine(); Text("Hello"); // <-- This was actually
659incorrect! BUT appeared to look ok with the default style where ItemSpacing.x == FramePadding.x * 2 (it didn't look
660aligned otherwise).
661 - new: TreeNode("##Hidden"); SameLine(0, 0); Text("Hello"); // <-- This is correct for all
662styles values. with the fix, IF you were successfully using TreeNode("")+SameLine(); you will now have extra spacing
663between your TreeNode and the following item. You'll need to change the SameLine() call to SameLine(0,0) to remove this
664extraneous spacing. This seemed like the more sensible fix that's not making things less consistent. (Note: when using
665this idiom you are likely to also use ImGuiTreeNodeFlags_SpanAvailWidth).
666 - 2024/03/18 (1.90.5) - merged the radius_x/radius_y parameters in ImDrawList::AddEllipse(), AddEllipseFilled() and
667PathEllipticalArcTo() into a single ImVec2 parameter. Exceptionally, because those functions were added in 1.90, we are
668not adding inline redirection functions. The transition is easy and should affect few users. (#2743, #7417)
669 - 2024/03/08 (1.90.5) - inputs: more formally obsoleted GetKeyIndex() when IMGUI_DISABLE_OBSOLETE_FUNCTIONS is set. It
670has been unnecessary and a no-op since 1.87 (it returns the same value as passed when used with a 1.87+ backend using
671io.AddKeyEvent() function). (#4921)
672 - IsKeyPressed(GetKeyIndex(ImGuiKey_XXX)) -> use IsKeyPressed(ImGuiKey_XXX)
673 - 2024/01/15 (1.90.2) - commented out obsolete ImGuiIO::ImeWindowHandle marked obsolete in 1.87, favor of writing to
674'void* ImGuiViewport::PlatformHandleRaw'.
675 - 2023/12/19 (1.90.1) - commented out obsolete ImGuiKey_KeyPadEnter redirection to ImGuiKey_KeypadEnter.
676 - 2023/11/06 (1.90.1) - removed CalcListClipping() marked obsolete in 1.86. Prefer using ImGuiListClipper which can
677return non-contiguous ranges.
678 - 2023/11/05 (1.90.1) - imgui_freetype: commented out ImGuiFreeType::BuildFontAtlas() obsoleted in 1.81. prefer using
679#define IMGUI_ENABLE_FREETYPE or see commented code for manual calls.
680 - 2023/11/05 (1.90.1) - internals,columns: commented out legacy ImGuiColumnsFlags_XXX symbols redirecting to
681ImGuiOldColumnsFlags_XXX, obsoleted from imgui_internal.h in 1.80.
682 - 2023/11/09 (1.90.0) - removed IM_OFFSETOF() macro in favor of using offsetof() available in C++11. Kept redirection
683define (will obsolete).
684 - 2023/11/07 (1.90.0) - removed BeginChildFrame()/EndChildFrame() in favor of using BeginChild() with the
685ImGuiChildFlags_FrameStyle flag. kept inline redirection function (will obsolete). those functions were merely
686PushStyle/PopStyle helpers, the removal isn't so much motivated by needing to add the feature in BeginChild(), but by
687the necessity to avoid BeginChildFrame() signature mismatching BeginChild() signature and features.
688 - 2023/11/02 (1.90.0) - BeginChild: upgraded 'bool border = true' parameter to 'ImGuiChildFlags flags' type, added
689ImGuiChildFlags_Border equivalent. As with our prior "bool-to-flags" API updates, the ImGuiChildFlags_Border value is
690guaranteed to be == true forever to ensure a smoother transition, meaning all existing calls will still work.
691 - old: BeginChild("Name", size, true)
692 - new: BeginChild("Name", size, ImGuiChildFlags_Border)
693 - old: BeginChild("Name", size, false)
694 - new: BeginChild("Name", size) or BeginChild("Name", 0) or BeginChild("Name", size,
695ImGuiChildFlags_None)
696 **AMEND FROM THE FUTURE: from 1.91.1, 'ImGuiChildFlags_Border' is called
697'ImGuiChildFlags_Borders'**
698 - 2023/11/02 (1.90.0) - BeginChild: added child-flag ImGuiChildFlags_AlwaysUseWindowPadding as a replacement for the
699window-flag ImGuiWindowFlags_AlwaysUseWindowPadding: the feature only ever made sense for BeginChild() anyhow.
700 - old: BeginChild("Name", size, 0, ImGuiWindowFlags_AlwaysUseWindowPadding);
701 - new: BeginChild("Name", size, ImGuiChildFlags_AlwaysUseWindowPadding, 0);
702 - 2023/09/27 (1.90.0) - io: removed io.MetricsActiveAllocations introduced in 1.63. Same as 'g.DebugMemAllocCount -
703g.DebugMemFreeCount' (still displayed in Metrics, unlikely to be accessed by end-user).
704 - 2023/09/26 (1.90.0) - debug tools: Renamed ShowStackToolWindow() ("Stack Tool") to ShowIDStackToolWindow() ("ID Stack
705Tool"), as earlier name was misleading. Kept inline redirection function. (#4631)
706 - 2023/09/15 (1.90.0) - ListBox, Combo: changed signature of "name getter" callback in old one-liner ListBox()/Combo()
707apis. kept inline redirection function (will obsolete).
708 - old: bool Combo(const char* label, int* current_item, bool (*getter)(void* user_data, int
709idx, const char** out_text), ...)
710 - new: bool Combo(const char* label, int* current_item, const char* (*getter)(void*
711user_data, int idx), ...);
712 - old: bool ListBox(const char* label, int* current_item, bool (*getting)(void* user_data,
713int idx, const char** out_text), ...);
714 - new: bool ListBox(const char* label, int* current_item, const char* (*getter)(void*
715user_data, int idx), ...);
716 - 2023/09/08 (1.90.0) - commented out obsolete redirecting functions:
717 - GetWindowContentRegionWidth() -> use GetWindowContentRegionMax().x -
718GetWindowContentRegionMin().x. Consider that generally 'GetContentRegionAvail().x' is more useful.
719 - ImDrawCornerFlags_XXX -> use ImDrawFlags_RoundCornersXXX flags. Read 1.82
720Changelog for details + grep commented names in sources.
721 - commented out runtime support for hardcoded ~0 or 0x01..0x0F rounding flags values for
722AddRect()/AddRectFilled()/PathRect()/AddImageRounded() -> use ImDrawFlags_RoundCornersXXX flags. Read 1.82 Changelog for
723details
724 - 2023/08/25 (1.89.9) - clipper: Renamed IncludeRangeByIndices() (also called ForceDisplayRangeByIndices()
725before 1.89.6) to IncludeItemsByIndex(). Kept inline redirection function. Sorry!
726 - 2023/07/12 (1.89.8) - ImDrawData: CmdLists now owned, changed from ImDrawList** to ImVector<ImDrawList*>. Majority of
727users shouldn't be affected, but you cannot compare to NULL nor reassign manually anymore. Instead use AddDrawList().
728(#6406, #4879, #1878)
729 - 2023/06/28 (1.89.7) - overlapping items: obsoleted 'SetItemAllowOverlap()' (called after item) in favor of calling
730'SetNextItemAllowOverlap()' (called before item). 'SetItemAllowOverlap()' didn't and couldn't work reliably since 1.89
731(2022-11-15).
732 - 2023/06/28 (1.89.7) - overlapping items: renamed 'ImGuiTreeNodeFlags_AllowItemOverlap' to
733'ImGuiTreeNodeFlags_AllowOverlap', 'ImGuiSelectableFlags_AllowItemOverlap' to 'ImGuiSelectableFlags_AllowOverlap'. Kept
734redirecting enums (will obsolete).
735 - 2023/06/28 (1.89.7) - overlapping items: IsItemHovered() now by default return false when querying an item using
736AllowOverlap mode which is being overlapped. Use ImGuiHoveredFlags_AllowWhenOverlappedByItem to revert to old behavior.
737 - 2023/06/28 (1.89.7) - overlapping items: Selectable and TreeNode don't allow overlap when active so overlapping
738widgets won't appear as hovered. While this fixes a common small visual issue, it also means that calling
739IsItemHovered() after a non-reactive elements - e.g. Text() - overlapping an active one may fail if you don't use
740IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem). (#6610)
741 - 2023/06/20 (1.89.7) - moved io.HoverDelayShort/io.HoverDelayNormal to style.HoverDelayShort/style.HoverDelayNormal.
742As the fields were added in 1.89 and expected to be left unchanged by most users, or only tweaked once during app
743initialization, we are exceptionally accepting the breakage.
744 - 2023/05/30 (1.89.6) - backends: renamed "imgui_impl_sdlrenderer.cpp" to "imgui_impl_sdlrenderer2.cpp" and
745"imgui_impl_sdlrenderer.h" to "imgui_impl_sdlrenderer2.h". This is in prevision for the future release of SDL3.
746 - 2023/05/22 (1.89.6) - listbox: commented out obsolete/redirecting functions that were marked obsolete more than two
747years ago:
748 - ListBoxHeader() -> use BeginListBox() (note how two variants of ListBoxHeader() existed.
749Check commented versions in imgui.h for reference)
750 - ListBoxFooter() -> use EndListBox()
751 - 2023/05/15 (1.89.6) - clipper: commented out obsolete redirection constructor 'ImGuiListClipper(int items_count,
752float items_height = -1.0f)' that was marked obsolete in 1.79. Use default constructor + clipper.Begin().
753 - 2023/05/15 (1.89.6) - clipper: renamed ImGuiListClipper::ForceDisplayRangeByIndices() to
754ImGuiListClipper::IncludeRangeByIndices().
755 - 2023/03/14 (1.89.4) - commented out redirecting enums/functions names that were marked obsolete two years ago:
756 - ImGuiSliderFlags_ClampOnInput -> use ImGuiSliderFlags_AlwaysClamp
757 - ImGuiInputTextFlags_AlwaysInsertMode -> use ImGuiInputTextFlags_AlwaysOverwrite
758 - ImDrawList::AddBezierCurve() -> use ImDrawList::AddBezierCubic()
759 - ImDrawList::PathBezierCurveTo() -> use ImDrawList::PathBezierCubicCurveTo()
760 - 2023/03/09 (1.89.4) - renamed PushAllowKeyboardFocus()/PopAllowKeyboardFocus() to PushTabStop()/PopTabStop(). Kept
761inline redirection functions (will obsolete).
762 - 2023/03/09 (1.89.4) - tooltips: Added 'bool' return value to BeginTooltip() for API consistency. Please only submit
763contents and call EndTooltip() if BeginTooltip() returns true. In reality the function will _currently_ always return
764true, but further changes down the line may change this, best to clarify API sooner.
765 - 2023/02/15 (1.89.4) - moved the optional "courtesy maths operators" implementation from imgui_internal.h in imgui.h.
766 Even though we encourage using your own maths types and operators by setting up
767IM_VEC2_CLASS_EXTRA, it has been frequently requested by people to use our own. We had an opt-in define which was
768 previously fulfilled in imgui_internal.h. It is now fulfilled in imgui.h. (#6164)
769 - OK: #define IMGUI_DEFINE_MATH_OPERATORS / #include "imgui.h" / #include
770"imgui_internal.h"
771 - Error: #include "imgui.h" / #define IMGUI_DEFINE_MATH_OPERATORS / #include
772"imgui_internal.h"
773 - 2023/02/07 (1.89.3) - backends: renamed "imgui_impl_sdl.cpp" to "imgui_impl_sdl2.cpp" and "imgui_impl_sdl.h" to
774"imgui_impl_sdl2.h". (#6146) This is in prevision for the future release of SDL3.
775 - 2022/10/26 (1.89) - commented out redirecting OpenPopupContextItem() which was briefly the name of
776OpenPopupOnItemClick() from 1.77 to 1.79.
777 - 2022/10/12 (1.89) - removed runtime patching of invalid "%f"/"%0.f" format strings for DragInt()/SliderInt(). This
778was obsoleted in 1.61 (May 2018). See 1.61 changelog for details.
779 - 2022/09/26 (1.89) - renamed and merged keyboard modifiers key enums and flags into a same set. Kept inline
780redirection enums (will obsolete).
781 - ImGuiKey_ModCtrl and ImGuiModFlags_Ctrl -> ImGuiMod_Ctrl
782 - ImGuiKey_ModShift and ImGuiModFlags_Shift -> ImGuiMod_Shift
783 - ImGuiKey_ModAlt and ImGuiModFlags_Alt -> ImGuiMod_Alt
784 - ImGuiKey_ModSuper and ImGuiModFlags_Super -> ImGuiMod_Super
785 the ImGuiKey_ModXXX were introduced in 1.87 and mostly used by backends.
786 the ImGuiModFlags_XXX have been exposed in imgui.h but not really used by any public api only
787by third-party extensions. exceptionally commenting out the older ImGuiKeyModFlags_XXX names ahead of obsolescence
788schedule to reduce confusion and because they were not meant to be used anyway.
789 - 2022/09/20 (1.89) - ImGuiKey is now a typed enum, allowing ImGuiKey_XXX symbols to be named in debuggers.
790 this will require uses of legacy backend-dependent indices to be casted, e.g.
791 - with imgui_impl_glfw: IsKeyPressed(GLFW_KEY_A) -> IsKeyPressed((ImGuiKey)GLFW_KEY_A);
792 - with imgui_impl_win32: IsKeyPressed('A') -> IsKeyPressed((ImGuiKey)'A')
793 - etc. However if you are upgrading code you might well use the better, backend-agnostic
794IsKeyPressed(ImGuiKey_A) now!
795 - 2022/09/12 (1.89) - removed the bizarre legacy default argument for 'TreePush(const void* ptr = NULL)', always pass a
796pointer value explicitly. NULL/nullptr is ok but require cast, e.g. TreePush((void*)nullptr);
797 - 2022/09/05 (1.89) - commented out redirecting functions/enums names that were marked obsolete in 1.77 and 1.78 (June
7982020):
799 - DragScalar(), DragScalarN(), DragFloat(), DragFloat2(), DragFloat3(), DragFloat4(): For old
800signatures ending with (..., const char* format, float power = 1.0f) -> use (..., format ImGuiSliderFlags_Logarithmic)
801if power != 1.0f.
802 - SliderScalar(), SliderScalarN(), SliderFloat(), SliderFloat2(), SliderFloat3(),
803SliderFloat4(): For old signatures ending with (..., const char* format, float power = 1.0f) -> use (..., format
804ImGuiSliderFlags_Logarithmic) if power != 1.0f.
805 - BeginPopupContextWindow(const char*, ImGuiMouseButton, bool) -> use
806BeginPopupContextWindow(const char*, ImGuiPopupFlags)
807 - 2022/09/02 (1.89) - obsoleted using SetCursorPos()/SetCursorScreenPos() to extend parent window/cell boundaries.
808 this relates to when moving the cursor position beyond current boundaries WITHOUT submitting an
809item.
810 - previously this would make the window content size ~200x200:
811 Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + End();
812 - instead, please submit an item:
813 Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) +
814Dummy(ImVec2(0,0)) + End();
815 - alternative:
816 Begin(...) + Dummy(ImVec2(200,200)) + End();
817 - content size is now only extended when submitting an item!
818 - with '#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS' this will now be detected and assert.
819 - without '#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS' this will silently be fixed until we
820obsolete it.
821 - 2022/08/03 (1.89) - changed signature of ImageButton() function. Kept redirection function (will obsolete).
822 - added 'const char* str_id' parameter + removed 'int frame_padding = -1' parameter.
823 - old signature: bool ImageButton(ImTextureID tex_id, ImVec2 size, ImVec2 uv0 = ImVec2(0,0),
824ImVec2 uv1 = ImVec2(1,1), int frame_padding = -1, ImVec4 bg_col = ImVec4(0,0,0,0), ImVec4 tint_col = ImVec4(1,1,1,1));
825 - used the ImTextureID value to create an ID. This was inconsistent with other functions, led
826to ID conflicts, and caused problems with engines using transient ImTextureID values.
827 - had a FramePadding override which was inconsistent with other functions and made the
828already-long signature even longer.
829 - new signature: bool ImageButton(const char* str_id, ImTextureID tex_id, ImVec2 size, ImVec2
830uv0 = ImVec2(0,0), ImVec2 uv1 = ImVec2(1,1), ImVec4 bg_col = ImVec4(0,0,0,0), ImVec4 tint_col = ImVec4(1,1,1,1));
831 - requires an explicit identifier. You may still use e.g. PushID() calls and then pass an
832empty identifier.
833 - always uses style.FramePadding for padding, to be consistent with other buttons. You may use
834PushStyleVar() to alter this.
835 - 2022/07/08 (1.89) - inputs: removed io.NavInputs[] and ImGuiNavInput enum (following 1.87 changes).
836 - Official backends from 1.87+ -> no issue.
837 - Official backends from 1.60 to 1.86 -> will build and convert gamepad inputs, unless
838IMGUI_DISABLE_OBSOLETE_KEYIO is defined. Need updating!
839 - Custom backends not writing to io.NavInputs[] -> no issue.
840 - Custom backends writing to io.NavInputs[] -> will build and convert gamepad inputs, unless
841IMGUI_DISABLE_OBSOLETE_KEYIO is defined. Need fixing!
842 - TL;DR: Backends should call io.AddKeyEvent()/io.AddKeyAnalogEvent() with ImGuiKey_GamepadXXX
843values instead of filling io.NavInput[].
844 - 2022/06/15 (1.88) - renamed IMGUI_DISABLE_METRICS_WINDOW to IMGUI_DISABLE_DEBUG_TOOLS for correctness. kept support
845for old define (will obsolete).
846 - 2022/05/03 (1.88) - backends: osx: removed ImGui_ImplOSX_HandleEvent() from backend API in favor of backend
847automatically handling event capture. All ImGui_ImplOSX_HandleEvent() calls should be removed as they are now
848unnecessary.
849 - 2022/04/05 (1.88) - inputs: renamed ImGuiKeyModFlags to ImGuiModFlags. Kept inline redirection enums (will obsolete).
850This was never used in public API functions but technically present in imgui.h and ImGuiIO.
851 - 2022/01/20 (1.87) - inputs: reworded gamepad IO.
852 - Backend writing to io.NavInputs[] -> backend should call
853io.AddKeyEvent()/io.AddKeyAnalogEvent() with ImGuiKey_GamepadXXX values.
854 - 2022/01/19 (1.87) - sliders, drags: removed support for legacy arithmetic operators (+,+-,*,/) when inputting text.
855This doesn't break any api/code but a feature that used to be accessible by end-users (which seemingly no one used).
856 - 2022/01/17 (1.87) - inputs: reworked mouse IO.
857 - Backend writing to io.MousePos -> backend should call io.AddMousePosEvent()
858 - Backend writing to io.MouseDown[] -> backend should call io.AddMouseButtonEvent()
859 - Backend writing to io.MouseWheel -> backend should call io.AddMouseWheelEvent()
860 - Backend writing to io.MouseHoveredViewport -> backend should call io.AddMouseViewportEvent()
861[Docking branch w/ multi-viewports only] note: for all calls to IO new functions, the Dear ImGui context should be
862bound/current. read https://github.com/ocornut/imgui/issues/4921 for details.
863 - 2022/01/10 (1.87) - inputs: reworked keyboard IO. Removed io.KeyMap[], io.KeysDown[] in favor of calling
864io.AddKeyEvent(), ImGui::IsKeyDown(). Removed GetKeyIndex(), now unnecessary. All IsKeyXXX() functions now take ImGuiKey
865values. All features are still functional until IMGUI_DISABLE_OBSOLETE_KEYIO is defined. Read Changelog and Release
866Notes for details.
867 - IsKeyPressed(MY_NATIVE_KEY_XXX) -> use IsKeyPressed(ImGuiKey_XXX)
868 - IsKeyPressed(GetKeyIndex(ImGuiKey_XXX)) -> use IsKeyPressed(ImGuiKey_XXX)
869 - Backend writing to io.KeyMap[],io.KeysDown[] -> backend should call io.AddKeyEvent() (+ call
870io.SetKeyEventNativeData() if you want legacy user code to still function with legacy key codes).
871 - Backend writing to io.KeyCtrl, io.KeyShift.. -> backend should call io.AddKeyEvent() with
872ImGuiMod_XXX values. *IF YOU PULLED CODE BETWEEN 2021/01/10 and 2021/01/27: We used to have a io.AddKeyModsEvent()
873function which was now replaced by io.AddKeyEvent() with ImGuiMod_XXX values.*
874 - one case won't work with backward compatibility: if your custom backend used ImGuiKey as mock
875native indices (e.g. "io.KeyMap[ImGuiKey_A] = ImGuiKey_A") because those values are now larger than the legacy KeyDown[]
876array. Will assert.
877 - inputs: added ImGuiKey_ModCtrl/ImGuiKey_ModShift/ImGuiKey_ModAlt/ImGuiKey_ModSuper values to
878submit keyboard modifiers using io.AddKeyEvent(), instead of writing directly to io.KeyCtrl, io.KeyShift, io.KeyAlt,
879io.KeySuper.
880 - 2022/01/05 (1.87) - inputs: renamed ImGuiKey_KeyPadEnter to ImGuiKey_KeypadEnter to align with new symbols. Kept
881redirection enum.
882 - 2022/01/05 (1.87) - removed io.ImeSetInputScreenPosFn() in favor of more flexible io.SetPlatformImeDataFn(). Removed
883'void* io.ImeWindowHandle' in favor of writing to 'void* ImGuiViewport::PlatformHandleRaw'.
884 - 2022/01/01 (1.87) - commented out redirecting functions/enums names that were marked obsolete
885in 1.69, 1.70, 1.71, 1.72 (March-July 2019)
886 - ImGui::SetNextTreeNodeOpen() -> use ImGui::SetNextItemOpen()
887 - ImGui::GetContentRegionAvailWidth() -> use ImGui::GetContentRegionAvail().x
888 - ImGui::TreeAdvanceToLabelPos() -> use ImGui::SetCursorPosX(ImGui::GetCursorPosX() +
889ImGui::GetTreeNodeToLabelSpacing());
890 - ImFontAtlas::CustomRect -> use ImFontAtlasCustomRect
891 - ImGuiColorEditFlags_RGB/HSV/HEX -> use ImGuiColorEditFlags_DisplayRGB/HSV/Hex
892 - 2021/12/20 (1.86) - backends: removed obsolete Marmalade backend (imgui_impl_marmalade.cpp) + example. Find last
893supported version at https://github.com/ocornut/imgui/wiki/Bindings
894 - 2021/11/04 (1.86) - removed CalcListClipping() function. Prefer using ImGuiListClipper which can return
895non-contiguous ranges. Please open an issue if you think you really need this function.
896 - 2021/08/23 (1.85) - removed GetWindowContentRegionWidth() function. keep inline redirection helper. can use
897'GetWindowContentRegionMax().x - GetWindowContentRegionMin().x' instead for generally 'GetContentRegionAvail().x' is
898more useful.
899 - 2021/07/26 (1.84) - commented out redirecting functions/enums names that were marked obsolete in 1.67 and 1.69 (March
9002019):
901 - ImGui::GetOverlayDrawList() -> use ImGui::GetForegroundDrawList()
902 - ImFont::GlyphRangesBuilder -> use ImFontGlyphRangesBuilder
903 - 2021/05/19 (1.83) - backends: obsoleted direct access to ImDrawCmd::TextureId in favor of calling
904ImDrawCmd::GetTexID().
905 - if you are using official backends from the source tree: you have nothing to do.
906 - if you have copied old backend code or using your own: change access to draw_cmd->TextureId to
907draw_cmd->GetTexID().
908 - 2021/03/12 (1.82) - upgraded ImDrawList::AddRect(), AddRectFilled(), PathRect() to use ImDrawFlags instead of
909ImDrawCornersFlags.
910 - ImDrawCornerFlags_TopLeft -> use ImDrawFlags_RoundCornersTopLeft
911 - ImDrawCornerFlags_BotRight -> use ImDrawFlags_RoundCornersBottomRight
912 - ImDrawCornerFlags_None -> use ImDrawFlags_RoundCornersNone etc.
913 flags now sanely defaults to 0 instead of 0x0F, consistent with all other flags in the API.
914 breaking: the default with rounding > 0.0f is now "round all corners" vs old implicit "round no
915corners":
916 - rounding == 0.0f + flags == 0 --> meant no rounding --> unchanged (common use)
917 - rounding > 0.0f + flags != 0 --> meant rounding --> unchanged (common use)
918 - rounding == 0.0f + flags != 0 --> meant no rounding --> unchanged (unlikely use)
919 - rounding > 0.0f + flags == 0 --> meant no rounding --> BREAKING (unlikely use): will now
920round all corners --> use ImDrawFlags_RoundCornersNone or rounding == 0.0f. this ONLY matters for hard coded use of 0 +
921rounding > 0.0f. Use of named ImDrawFlags_RoundCornersNone (new) or ImDrawCornerFlags_None (old) are ok. the old
922ImDrawCornersFlags used awkward default values of ~0 or 0xF (4 lower bits set) to signify "round all corners" and we
923sometimes encouraged using them as shortcuts. legacy path still support use of hard coded ~0 or any value from 0x1 or
9240xF. They will behave the same with legacy paths enabled (will assert otherwise).
925 - 2021/03/11 (1.82) - removed redirecting functions/enums names that were marked obsolete in 1.66 (September 2018):
926 - ImGui::SetScrollHere() -> use ImGui::SetScrollHereY()
927 - 2021/03/11 (1.82) - clarified that ImDrawList::PathArcTo(), ImDrawList::PathArcToFast() won't render with radius <
9280.0f. Previously it sorts of accidentally worked but would generally lead to counter-clockwise paths and have an effect
929on anti-aliasing.
930 - 2021/03/10 (1.82) - upgraded ImDrawList::AddPolyline() and PathStroke() "bool closed" parameter to "ImDrawFlags
931flags". The matching ImDrawFlags_Closed value is guaranteed to always stay == 1 in the future.
932 - 2021/02/22 (1.82) - (*undone in 1.84*) win32+mingw: Re-enabled IME functions by default even under MinGW. In July
9332016, issue #738 had me incorrectly disable those default functions for MinGW. MinGW users should: either link with
934-limm32, either set their imconfig file with '#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS'.
935 - 2021/02/17 (1.82) - renamed rarely used style.CircleSegmentMaxError (old default = 1.60f) to
936style.CircleTessellationMaxError (new default = 0.30f) as the meaning of the value changed.
937 - 2021/02/03 (1.81) - renamed ListBoxHeader(const char* label, ImVec2 size) to BeginListBox(). Kept inline redirection
938function (will obsolete).
939 - removed ListBoxHeader(const char* label, int items_count, int height_in_items = -1) in favor of
940specifying size. Kept inline redirection function (will obsolete).
941 - renamed ListBoxFooter() to EndListBox(). Kept inline redirection function (will obsolete).
942 - 2021/01/26 (1.81) - removed ImGuiFreeType::BuildFontAtlas(). Kept inline redirection function. Prefer using '#define
943IMGUI_ENABLE_FREETYPE', but there's a runtime selection path available too. The shared extra flags parameters (very
944rarely used) are now stored in ImFontAtlas::FontBuilderFlags.
945 - renamed ImFontConfig::RasterizerFlags (used by FreeType) to ImFontConfig::FontBuilderFlags.
946 - renamed ImGuiFreeType::XXX flags to ImGuiFreeTypeBuilderFlags_XXX for consistency with other API.
947 - 2020/10/12 (1.80) - removed redirecting functions/enums that were marked obsolete in 1.63 (August 2018):
948 - ImGui::IsItemDeactivatedAfterChange() -> use ImGui::IsItemDeactivatedAfterEdit().
949 - ImGuiCol_ModalWindowDarkening -> use ImGuiCol_ModalWindowDimBg
950 - ImGuiInputTextCallback -> use ImGuiTextEditCallback
951 - ImGuiInputTextCallbackData -> use ImGuiTextEditCallbackData
952 - 2020/12/21 (1.80) - renamed ImDrawList::AddBezierCurve() to AddBezierCubic(), and PathBezierCurveTo() to
953PathBezierCubicCurveTo(). Kept inline redirection function (will obsolete).
954 - 2020/12/04 (1.80) - added imgui_tables.cpp file! Manually constructed project files will need the new file added!
955 - 2020/11/18 (1.80) - renamed undocumented/internals ImGuiColumnsFlags_* to ImGuiOldColumnFlags_* in prevision of
956incoming Tables API.
957 - 2020/11/03 (1.80) - renamed io.ConfigWindowsMemoryCompactTimer to io.ConfigMemoryCompactTimer as the feature will
958apply to other data structures
959 - 2020/10/14 (1.80) - backends: moved all backends files (imgui_impl_XXXX.cpp, imgui_impl_XXXX.h) from examples/ to
960backends/.
961 - 2020/10/12 (1.80) - removed redirecting functions/enums that were marked obsolete in 1.60 (April 2018):
962 - io.RenderDrawListsFn pointer -> use ImGui::GetDrawData() value and call the render
963function of your backend
964 - ImGui::IsAnyWindowFocused() -> use ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow)
965 - ImGui::IsAnyWindowHovered() -> use ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow)
966 - ImGuiStyleVar_Count_ -> use ImGuiStyleVar_COUNT
967 - ImGuiMouseCursor_Count_ -> use ImGuiMouseCursor_COUNT
968 - removed redirecting functions names that were marked obsolete in 1.61 (May 2018):
969 - InputFloat (... int decimal_precision ...) -> use InputFloat (... const char* format ...) with
970format = "%.Xf" where X is your value for decimal_precision.
971 - same for InputFloat2()/InputFloat3()/InputFloat4() variants taking a `int decimal_precision`
972parameter.
973 - 2020/10/05 (1.79) - removed ImGuiListClipper: Renamed constructor parameters which created an ambiguous alternative
974to using the ImGuiListClipper::Begin() function, with misleading edge cases (note: imgui_memory_editor <0.40 from
975imgui_club/ used this old clipper API. Update your copy if needed).
976 - 2020/09/25 (1.79) - renamed ImGuiSliderFlags_ClampOnInput to ImGuiSliderFlags_AlwaysClamp. Kept redirection enum
977(will obsolete sooner because previous name was added recently).
978 - 2020/09/25 (1.79) - renamed style.TabMinWidthForUnselectedCloseButton to style.TabMinWidthForCloseButton.
979 - 2020/09/21 (1.79) - renamed OpenPopupContextItem() back to OpenPopupOnItemClick(), reverting the change from 1.77.
980For varieties of reason this is more self-explanatory.
981 - 2020/09/21 (1.79) - removed return value from OpenPopupOnItemClick() - returned true on mouse release on an item -
982because it is inconsistent with other popup APIs and makes others misleading. It's also and unnecessary: you can use
983IsWindowAppearing() after BeginPopup() for a similar result.
984 - 2020/09/17 (1.79) - removed ImFont::DisplayOffset in favor of ImFontConfig::GlyphOffset. DisplayOffset was applied
985after scaling and not very meaningful/useful outside of being needed by the default ProggyClean font. If you scaled this
986value after calling AddFontDefault(), this is now done automatically. It was also getting in the way of better font
987scaling, so let's get rid of it now!
988 - 2020/08/17 (1.78) - obsoleted use of the trailing 'float power=1.0f' parameter for DragFloat(), DragFloat2(),
989DragFloat3(), DragFloat4(), DragFloatRange2(), DragScalar(), DragScalarN(), SliderFloat(), SliderFloat2(),
990SliderFloat3(), SliderFloat4(), SliderScalar(), SliderScalarN(), VSliderFloat() and VSliderScalar(). replaced the 'float
991power=1.0f' argument with integer-based flags defaulting to 0 (as with all our flags). worked out a
992backward-compatibility scheme so hopefully most C++ codebase should not be affected. in short, when calling those
993functions:
994 - if you omitted the 'power' parameter (likely!), you are not affected.
995 - if you set the 'power' parameter to 1.0f (same as previous default value): 1/ your compiler may
996warn on float>int conversion, 2/ everything else will work. 3/ you can replace the 1.0f value with 0 to fix the warning,
997and be technically correct.
998 - if you set the 'power' parameter to >1.0f (to enable non-linear editing): 1/ your compiler may
999warn on float>int conversion, 2/ code will assert at runtime, 3/ in case asserts are disabled, the code will not crash
1000and enable the _Logarithmic flag. 4/ you can replace the >1.0f value with ImGuiSliderFlags_Logarithmic to fix the
1001warning/assert and get a _similar_ effect as previous uses of power >1.0f. see
1002https://github.com/ocornut/imgui/issues/3361 for all details. kept inline redirection functions (will obsolete) apart
1003for: DragFloatRange2(), VSliderFloat(), VSliderScalar(). For those three the 'float power=1.0f' version was removed
1004directly as they were most unlikely ever used. for shared code, you can version check at compile-time with `#if
1005IMGUI_VERSION_NUM >= 17704`.
1006 - obsoleted use of v_min > v_max in DragInt, DragFloat, DragScalar to lock edits (introduced
1007in 1.73, was not demoed nor documented very), will be replaced by a more generic ReadOnly feature. You may use the
1008ImGuiSliderFlags_ReadOnly internal flag in the meantime.
1009 - 2020/06/23 (1.77) - removed BeginPopupContextWindow(const char*, int mouse_button, bool also_over_items) in favor of
1010BeginPopupContextWindow(const char*, ImGuiPopupFlags flags) with ImGuiPopupFlags_NoOverItems.
1011 - 2020/06/15 (1.77) - renamed OpenPopupOnItemClick() to OpenPopupContextItem(). Kept inline redirection function (will
1012obsolete). [NOTE: THIS WAS REVERTED IN 1.79]
1013 - 2020/06/15 (1.77) - removed CalcItemRectClosestPoint() entry point which was made obsolete and asserting in December
10142017.
1015 - 2020/04/23 (1.77) - removed unnecessary ID (first arg) of ImFontAtlas::AddCustomRectRegular().
1016 - 2020/01/22 (1.75) - ImDrawList::AddCircle()/AddCircleFilled() functions don't accept negative radius any more.
1017 - 2019/12/17 (1.75) - [undid this change in 1.76] made Columns() limited to 64 columns by asserting above that limit.
1018While the current code technically supports it, future code may not so we're putting the restriction ahead.
1019 - 2019/12/13 (1.75) - [imgui_internal.h] changed ImRect() default constructor initializes all fields to 0.0f instead of
1020(FLT_MAX,FLT_MAX,-FLT_MAX,-FLT_MAX). If you used ImRect::Add() to create bounding boxes by adding multiple points into
1021it, you may need to fix your initial value.
1022 - 2019/12/08 (1.75) - removed redirecting functions/enums that were marked obsolete in 1.53 (December 2017):
1023 - ShowTestWindow() -> use ShowDemoWindow()
1024 - IsRootWindowFocused() -> use IsWindowFocused(ImGuiFocusedFlags_RootWindow)
1025 - IsRootWindowOrAnyChildFocused() -> use
1026IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)
1027 - SetNextWindowContentWidth(w) -> use SetNextWindowContentSize(ImVec2(w, 0.0f)
1028 - GetItemsLineHeightWithSpacing() -> use GetFrameHeightWithSpacing()
1029 - ImGuiCol_ChildWindowBg -> use ImGuiCol_ChildBg
1030 - ImGuiStyleVar_ChildWindowRounding -> use ImGuiStyleVar_ChildRounding
1031 - ImGuiTreeNodeFlags_AllowOverlapMode -> use ImGuiTreeNodeFlags_AllowItemOverlap
1032 - IMGUI_DISABLE_TEST_WINDOWS -> use IMGUI_DISABLE_DEMO_WINDOWS
1033 - 2019/12/08 (1.75) - obsoleted calling ImDrawList::PrimReserve() with a negative count (which was vaguely documented
1034and rarely if ever used). Instead, we added an explicit PrimUnreserve() API.
1035 - 2019/12/06 (1.75) - removed implicit default parameter to IsMouseDragging(int button = 0) to be consistent with other
1036mouse functions (none of the other functions have it).
1037 - 2019/11/21 (1.74) - ImFontAtlas::AddCustomRectRegular() now requires an ID larger than 0x110000 (instead of 0x10000)
1038to conform with supporting Unicode planes 1-16 in a future update. ID below 0x110000 will now assert.
1039 - 2019/11/19 (1.74) - renamed IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS to IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS for
1040consistency.
1041 - 2019/11/19 (1.74) - renamed IMGUI_DISABLE_MATH_FUNCTIONS to IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS for consistency.
1042 - 2019/10/22 (1.74) - removed redirecting functions/enums that were marked obsolete in 1.52 (October 2017):
1043 - Begin() [old 5 args version] -> use Begin() [3 args], use SetNextWindowSize()
1044SetNextWindowBgAlpha() if needed
1045 - IsRootWindowOrAnyChildHovered() -> use
1046IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows)
1047 - AlignFirstTextHeightToWidgets() -> use AlignTextToFramePadding()
1048 - SetNextWindowPosCenter() -> use SetNextWindowPos() with a pivot of (0.5f, 0.5f)
1049 - ImFont::Glyph -> use ImFontGlyph
1050 - 2019/10/14 (1.74) - inputs: Fixed a miscalculation in the keyboard/mouse "typematic" repeat delay/rate calculation,
1051used by keys and e.g. repeating mouse buttons as well as the GetKeyPressedAmount() function. if you were using a
1052non-default value for io.KeyRepeatRate (previous default was 0.250), you can add +io.KeyRepeatDelay to it to compensate
1053for the fix. The function was triggering on: 0.0 and (delay+rate*N) where (N>=1). Fixed formula responds to (N>=0).
1054Effectively it made io.KeyRepeatRate behave like it was set to (io.KeyRepeatRate + io.KeyRepeatDelay). If you never
1055altered io.KeyRepeatRate nor used GetKeyPressedAmount() this won't affect you.
1056 - 2019/07/15 (1.72) - removed TreeAdvanceToLabelPos() which is rarely used and only does SetCursorPosX(GetCursorPosX()
1057+ GetTreeNodeToLabelSpacing()). Kept redirection function (will obsolete).
1058 - 2019/07/12 (1.72) - renamed ImFontAtlas::CustomRect to ImFontAtlasCustomRect. Kept redirection typedef (will
1059obsolete).
1060 - 2019/06/14 (1.72) - removed redirecting functions/enums names that were marked obsolete in 1.51 (June 2017):
1061ImGuiCol_Column*, ImGuiSetCond_*, IsItemHoveredRect(), IsPosHoveringAnyWindow(), IsMouseHoveringAnyWindow(),
1062IsMouseHoveringWindow(), IMGUI_ONCE_UPON_A_FRAME. Grep this log for details and new names, or see how they were
1063implemented until 1.71.
1064 - 2019/06/07 (1.71) - rendering of child window outer decorations (bg color, border, scrollbars) is now performed as
1065part of the parent window. If you have overlapping child windows in a same parent, and relied on their relative z-order
1066to be mapped to their submission order, this will affect your rendering. This optimization is disabled if the parent
1067window has no visual output, because it appears to be the most common situation leading to the creation of overlapping
1068child windows. Please reach out if you are affected.
1069 - 2019/05/13 (1.71) - renamed SetNextTreeNodeOpen() to SetNextItemOpen(). Kept inline redirection function (will
1070obsolete).
1071 - 2019/05/11 (1.71) - changed io.AddInputCharacter(unsigned short c) signature to io.AddInputCharacter(unsigned int c).
1072 - 2019/04/29 (1.70) - improved ImDrawList thick strokes (>1.0f) preserving correct thickness up to 90 degrees angles
1073(e.g. rectangles). If you have custom rendering using thick lines, they will appear thicker now.
1074 - 2019/04/29 (1.70) - removed GetContentRegionAvailWidth(), use GetContentRegionAvail().x instead. Kept inline
1075redirection function (will obsolete).
1076 - 2019/03/04 (1.69) - renamed GetOverlayDrawList() to GetForegroundDrawList(). Kept redirection function (will
1077obsolete).
1078 - 2019/02/26 (1.69) - renamed ImGuiColorEditFlags_RGB/ImGuiColorEditFlags_HSV/ImGuiColorEditFlags_HEX to
1079ImGuiColorEditFlags_DisplayRGB/ImGuiColorEditFlags_DisplayHSV/ImGuiColorEditFlags_DisplayHex. Kept redirection enums
1080(will obsolete).
1081 - 2019/02/14 (1.68) - made it illegal/assert when io.DisplayTime == 0.0f (with an exception for the first frame). If
1082for some reason your time step calculation gives you a zero value, replace it with an arbitrarily small value!
1083 - 2019/02/01 (1.68) - removed io.DisplayVisibleMin/DisplayVisibleMax (which were marked obsolete and removed from
1084viewport/docking branch already).
1085 - 2019/01/06 (1.67) - renamed io.InputCharacters[], marked internal as was always intended. Please don't access
1086directly, and use AddInputCharacter() instead!
1087 - 2019/01/06 (1.67) - renamed ImFontAtlas::GlyphRangesBuilder to ImFontGlyphRangesBuilder. Kept redirection typedef
1088(will obsolete).
1089 - 2018/12/20 (1.67) - made it illegal to call Begin("") with an empty string. This somehow half-worked before but had
1090various undesirable side-effects.
1091 - 2018/12/10 (1.67) - renamed io.ConfigResizeWindowsFromEdges to io.ConfigWindowsResizeFromEdges as we are doing a
1092large pass on configuration flags.
1093 - 2018/10/12 (1.66) - renamed misc/stl/imgui_stl.* to misc/cpp/imgui_stdlib.* in prevision for other C++ helper files.
1094 - 2018/09/28 (1.66) - renamed SetScrollHere() to SetScrollHereY(). Kept redirection function (will obsolete).
1095 - 2018/09/06 (1.65) - renamed stb_truetype.h to imstb_truetype.h, stb_textedit.h to imstb_textedit.h, and
1096stb_rect_pack.h to imstb_rectpack.h. If you were conveniently using the imgui copy of those STB headers in your project
1097you will have to update your include paths.
1098 - 2018/09/05 (1.65) - renamed io.OptCursorBlink/io.ConfigCursorBlink to io.ConfigInputTextCursorBlink. (#1427)
1099 - 2018/08/31 (1.64) - added imgui_widgets.cpp file, extracted and moved widgets code out of imgui.cpp into
1100imgui_widgets.cpp. Re-ordered some of the code remaining in imgui.cpp. NONE OF THE FUNCTIONS HAVE CHANGED. THE CODE IS
1101SEMANTICALLY 100% IDENTICAL, BUT _EVERY_ FUNCTION HAS BEEN MOVED. Because of this, any local modifications to imgui.cpp
1102will likely conflict when you update. Read docs/CHANGELOG.txt for suggestions.
1103 - 2018/08/22 (1.63) - renamed IsItemDeactivatedAfterChange() to IsItemDeactivatedAfterEdit() for consistency with new
1104IsItemEdited() API. Kept redirection function (will obsolete soonish as IsItemDeactivatedAfterChange() is very recent).
1105 - 2018/08/21 (1.63) - renamed ImGuiTextEditCallback to ImGuiInputTextCallback, ImGuiTextEditCallbackData to
1106ImGuiInputTextCallbackData for consistency. Kept redirection types (will obsolete).
1107 - 2018/08/21 (1.63) - removed ImGuiInputTextCallbackData::ReadOnly since it is a duplication of
1108(ImGuiInputTextCallbackData::Flags & ImGuiInputTextFlags_ReadOnly).
1109 - 2018/08/01 (1.63) - removed per-window ImGuiWindowFlags_ResizeFromAnySide beta flag in favor of a global
1110io.ConfigResizeWindowsFromEdges [update 1.67 renamed to ConfigWindowsResizeFromEdges] to enable the feature.
1111 - 2018/08/01 (1.63) - renamed io.OptCursorBlink to io.ConfigCursorBlink [-> io.ConfigInputTextCursorBlink in 1.65],
1112io.OptMacOSXBehaviors to ConfigMacOSXBehaviors for consistency.
1113 - 2018/07/22 (1.63) - changed ImGui::GetTime() return value from float to double to avoid accumulating floating point
1114imprecisions over time.
1115 - 2018/07/08 (1.63) - style: renamed ImGuiCol_ModalWindowDarkening to ImGuiCol_ModalWindowDimBg for consistency with
1116other features. Kept redirection enum (will obsolete).
1117 - 2018/06/08 (1.62) - examples: the imgui_impl_XXX files have been split to separate platform (Win32, GLFW, SDL2, etc.)
1118from renderer (DX11, OpenGL, Vulkan, etc.). old backends will still work as is, however prefer using the separated
1119backends as they will be updated to support multi-viewports. when adopting new backends follow the main.cpp code of your
1120preferred examples/ folder to know which functions to call. in particular, note that old backends called
1121ImGui::NewFrame() at the end of their ImGui_ImplXXXX_NewFrame() function.
1122 - 2018/06/06 (1.62) - renamed GetGlyphRangesChinese() to GetGlyphRangesChineseFull() to distinguish other variants and
1123discourage using the full set.
1124 - 2018/06/06 (1.62) - TreeNodeEx()/TreeNodeBehavior(): the ImGuiTreeNodeFlags_CollapsingHeader helper now include the
1125ImGuiTreeNodeFlags_NoTreePushOnOpen flag. See Changelog for details.
1126 - 2018/05/03 (1.61) - DragInt(): the default compile-time format string has been changed from "%.0f" to "%d", as we are
1127not using integers internally any more. If you used DragInt() with custom format strings, make sure you change them to
1128use %d or an integer-compatible format. To honor backward-compatibility, the DragInt() code will currently parse and
1129modify format strings to replace %*f with %d, giving time to users to upgrade their code. If you have
1130IMGUI_DISABLE_OBSOLETE_FUNCTIONS enabled, the code will instead assert! You may run a reg-exp search on your codebase
1131for e.g. "DragInt.*%f" to help you find them.
1132 - 2018/04/28 (1.61) - obsoleted InputFloat() functions taking an optional "int decimal_precision" in favor of an
1133equivalent and more flexible "const char* format", consistent with other functions. Kept redirection functions (will
1134obsolete).
1135 - 2018/04/09 (1.61) - IM_DELETE() helper function added in 1.60 doesn't clear the input _pointer_ reference, more
1136consistent with expectation and allows passing r-value.
1137 - 2018/03/20 (1.60) - renamed io.WantMoveMouse to io.WantSetMousePos for consistency and ease of understanding (was
1138added in 1.52, _not_ used by core and only honored by some backend ahead of merging the Nav branch).
1139 - 2018/03/12 (1.60) - removed ImGuiCol_CloseButton, ImGuiCol_CloseButtonActive, ImGuiCol_CloseButtonHovered as the
1140closing cross uses regular button colors now.
1141 - 2018/03/08 (1.60) - changed ImFont::DisplayOffset.y to default to 0 instead of +1. Fixed rounding of Ascent/Descent
1142to match TrueType renderer. If you were adding or subtracting to ImFont::DisplayOffset check if your fonts are correctly
1143aligned vertically.
1144 - 2018/03/03 (1.60) - renamed ImGuiStyleVar_Count_ to ImGuiStyleVar_COUNT and ImGuiMouseCursor_Count_ to
1145ImGuiMouseCursor_COUNT for consistency with other public enums.
1146 - 2018/02/18 (1.60) - BeginDragDropSource(): temporarily removed the optional mouse_button=0 parameter because it is
1147not really usable in many situations at the moment.
1148 - 2018/02/16 (1.60) - obsoleted the io.RenderDrawListsFn callback, you can call your graphics engine render function
1149after ImGui::Render(). Use ImGui::GetDrawData() to retrieve the ImDrawData* to display.
1150 - 2018/02/07 (1.60) - reorganized context handling to be more explicit,
1151 - YOU NOW NEED TO CALL ImGui::CreateContext() AT THE BEGINNING OF YOUR APP, AND CALL
1152ImGui::DestroyContext() AT THE END.
1153 - removed Shutdown() function, as DestroyContext() serve this purpose.
1154 - you may pass a ImFontAtlas* pointer to CreateContext() to share a font atlas between contexts.
1155Otherwise CreateContext() will create its own font atlas instance.
1156 - removed allocator parameters from CreateContext(), they are now setup with
1157SetAllocatorFunctions(), and shared by all contexts.
1158 - removed the default global context and font atlas instance, which were confusing for users of
1159DLL reloading and users of multiple contexts.
1160 - 2018/01/31 (1.60) - moved sample TTF files from extra_fonts/ to misc/fonts/. If you loaded files directly from the
1161imgui repo you may need to update your paths.
1162 - 2018/01/11 (1.60) - obsoleted IsAnyWindowHovered() in favor of IsWindowHovered(ImGuiHoveredFlags_AnyWindow). Kept
1163redirection function (will obsolete).
1164 - 2018/01/11 (1.60) - obsoleted IsAnyWindowFocused() in favor of IsWindowFocused(ImGuiFocusedFlags_AnyWindow). Kept
1165redirection function (will obsolete).
1166 - 2018/01/03 (1.60) - renamed ImGuiSizeConstraintCallback to ImGuiSizeCallback, ImGuiSizeConstraintCallbackData to
1167ImGuiSizeCallbackData.
1168 - 2017/12/29 (1.60) - removed CalcItemRectClosestPoint() which was weird and not really used by anyone except demo
1169code. If you need it it's easy to replicate on your side.
1170 - 2017/12/24 (1.53) - renamed the emblematic ShowTestWindow() function to ShowDemoWindow(). Kept redirection function
1171(will obsolete).
1172 - 2017/12/21 (1.53) - ImDrawList: renamed style.AntiAliasedShapes to style.AntiAliasedFill for consistency and as a way
1173to explicitly break code that manipulate those flag at runtime. You can now manipulate ImDrawList::Flags
1174 - 2017/12/21 (1.53) - ImDrawList: removed 'bool anti_aliased = true' final parameter of ImDrawList::AddPolyline() and
1175ImDrawList::AddConvexPolyFilled(). Prefer manipulating ImDrawList::Flags if you need to toggle them during the frame.
1176 - 2017/12/14 (1.53) - using the ImGuiWindowFlags_NoScrollWithMouse flag on a child window forwards the mouse wheel
1177event to the parent window, unless either ImGuiWindowFlags_NoInputs or ImGuiWindowFlags_NoScrollbar are also set.
1178 - 2017/12/13 (1.53) - renamed GetItemsLineHeightWithSpacing() to GetFrameHeightWithSpacing(). Kept redirection function
1179(will obsolete).
1180 - 2017/12/13 (1.53) - obsoleted IsRootWindowFocused() in favor of using IsWindowFocused(ImGuiFocusedFlags_RootWindow).
1181Kept redirection function (will obsolete).
1182 - obsoleted IsRootWindowOrAnyChildFocused() in favor of using
1183IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows). Kept redirection function (will obsolete).
1184 - 2017/12/12 (1.53) - renamed ImGuiTreeNodeFlags_AllowOverlapMode to ImGuiTreeNodeFlags_AllowItemOverlap. Kept
1185redirection enum (will obsolete).
1186 - 2017/12/10 (1.53) - removed SetNextWindowContentWidth(), prefer using SetNextWindowContentSize(). Kept redirection
1187function (will obsolete).
1188 - 2017/11/27 (1.53) - renamed ImGuiTextBuffer::append() helper to appendf(), appendv() to appendfv(). If you copied the
1189'Log' demo in your code, it uses appendv() so that needs to be renamed.
1190 - 2017/11/18 (1.53) - Style, Begin: removed ImGuiWindowFlags_ShowBorders window flag. Borders are now fully set up in
1191the ImGuiStyle structure (see e.g. style.FrameBorderSize, style.WindowBorderSize). Use ImGui::ShowStyleEditor() to look
1192them up. Please note that the style system will keep evolving (hopefully stabilizing in Q1 2018), and so custom styles
1193will probably subtly break over time. It is recommended you use the StyleColorsClassic(), StyleColorsDark(),
1194StyleColorsLight() functions.
1195 - 2017/11/18 (1.53) - Style: removed ImGuiCol_ComboBg in favor of combo boxes using ImGuiCol_PopupBg for consistency.
1196 - 2017/11/18 (1.53) - Style: renamed ImGuiCol_ChildWindowBg to ImGuiCol_ChildBg.
1197 - 2017/11/18 (1.53) - Style: renamed style.ChildWindowRounding to style.ChildRounding,
1198ImGuiStyleVar_ChildWindowRounding to ImGuiStyleVar_ChildRounding.
1199 - 2017/11/02 (1.53) - obsoleted IsRootWindowOrAnyChildHovered() in favor of using
1200IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows);
1201 - 2017/10/24 (1.52) - renamed IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCS/IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCS to
1202IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS/IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS for consistency.
1203 - 2017/10/20 (1.52) - changed IsWindowHovered() default parameters behavior to return false if an item is active in
1204another window (e.g. click-dragging item from another window to this window). You can use the newly introduced
1205IsWindowHovered() flags to requests this specific behavior if you need it.
1206 - 2017/10/20 (1.52) - marked IsItemHoveredRect()/IsMouseHoveringWindow() as obsolete, in favor of using the newly
1207introduced flags for IsItemHovered() and IsWindowHovered(). See https://github.com/ocornut/imgui/issues/1382 for
1208details. removed the IsItemRectHovered()/IsWindowRectHovered() names introduced in 1.51 since they were merely more
1209consistent names for the two functions we are now obsoleting. IsItemHoveredRect() -->
1210IsItemHovered(ImGuiHoveredFlags_RectOnly) IsMouseHoveringAnyWindow() --> IsWindowHovered(ImGuiHoveredFlags_AnyWindow)
1211 IsMouseHoveringWindow() --> IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup |
1212ImGuiHoveredFlags_AllowWhenBlockedByActiveItem) [weird, old behavior]
1213 - 2017/10/17 (1.52) - marked the old 5-parameters version of Begin() as obsolete (still available). Use
1214SetNextWindowSize()+Begin() instead!
1215 - 2017/10/11 (1.52) - renamed AlignFirstTextHeightToWidgets() to AlignTextToFramePadding(). Kept inline redirection
1216function (will obsolete).
1217 - 2017/09/26 (1.52) - renamed ImFont::Glyph to ImFontGlyph. Kept redirection typedef (will obsolete).
1218 - 2017/09/25 (1.52) - removed SetNextWindowPosCenter() because SetNextWindowPos() now has the optional pivot
1219information to do the same and more. Kept redirection function (will obsolete).
1220 - 2017/08/25 (1.52) - io.MousePos needs to be set to ImVec2(-FLT_MAX,-FLT_MAX) when mouse is unavailable/missing.
1221Previously ImVec2(-1,-1) was enough but we now accept negative mouse coordinates. In your backend if you need to support
1222unavailable mouse, make sure to replace "io.MousePos = ImVec2(-1,-1)" with "io.MousePos = ImVec2(-FLT_MAX,-FLT_MAX)".
1223 - 2017/08/22 (1.51) - renamed IsItemHoveredRect() to IsItemRectHovered(). Kept inline redirection function (will
1224obsolete). -> (1.52) use IsItemHovered(ImGuiHoveredFlags_RectOnly)!
1225 - renamed IsMouseHoveringAnyWindow() to IsAnyWindowHovered() for consistency. Kept inline
1226redirection function (will obsolete).
1227 - renamed IsMouseHoveringWindow() to IsWindowRectHovered() for consistency. Kept inline redirection
1228function (will obsolete).
1229 - 2017/08/20 (1.51) - renamed GetStyleColName() to GetStyleColorName() for consistency.
1230 - 2017/08/20 (1.51) - added PushStyleColor(ImGuiCol idx, ImU32 col) overload, which _might_ cause an "ambiguous call"
1231compilation error if you are using ImColor() with implicit cast. Cast to ImU32 or ImVec4 explicitly to fix.
1232 - 2017/08/15 (1.51) - marked the weird IMGUI_ONCE_UPON_A_FRAME helper macro as obsolete. prefer using the more explicit
1233ImGuiOnceUponAFrame type.
1234 - 2017/08/15 (1.51) - changed parameter order for BeginPopupContextWindow() from (const char*,int buttons,bool
1235also_over_items) to (const char*,int buttons,bool also_over_items). Note that most calls relied on default parameters
1236completely.
1237 - 2017/08/13 (1.51) - renamed ImGuiCol_Column to ImGuiCol_Separator, ImGuiCol_ColumnHovered to
1238ImGuiCol_SeparatorHovered, ImGuiCol_ColumnActive to ImGuiCol_SeparatorActive. Kept redirection enums (will obsolete).
1239 - 2017/08/11 (1.51) - renamed ImGuiSetCond_Always to ImGuiCond_Always, ImGuiSetCond_Once to ImGuiCond_Once,
1240ImGuiSetCond_FirstUseEver to ImGuiCond_FirstUseEver, ImGuiSetCond_Appearing to ImGuiCond_Appearing. Kept redirection
1241enums (will obsolete).
1242 - 2017/08/09 (1.51) - removed ValueColor() helpers, they are equivalent to calling Text(label) + SameLine() +
1243ColorButton().
1244 - 2017/08/08 (1.51) - removed ColorEditMode() and ImGuiColorEditMode in favor of ImGuiColorEditFlags and parameters to
1245the various Color*() functions. The SetColorEditOptions() allows to initialize default but the user can still change
1246them with right-click context menu.
1247 - changed prototype of 'ColorEdit4(const char* label, float col[4], bool show_alpha = true)' to
1248'ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags = 0)', where passing flags = 0x01 is a safe no-op
1249(hello dodgy backward compatibility!). - check and run the demo window, under "Color/Picker Widgets", to understand the
1250various new options.
1251 - changed prototype of rarely used 'ColorButton(ImVec4 col, bool small_height = false, bool
1252outline_border = true)' to 'ColorButton(const char* desc_id, ImVec4 col, ImGuiColorEditFlags flags = 0, ImVec2 size =
1253ImVec2(0, 0))'
1254 - 2017/07/20 (1.51) - removed IsPosHoveringAnyWindow(ImVec2), which was partly broken and misleading. ASSERT + redirect
1255user to io.WantCaptureMouse
1256 - 2017/05/26 (1.50) - removed ImFontConfig::MergeGlyphCenterV in favor of a more multipurpose
1257ImFontConfig::GlyphOffset.
1258 - 2017/05/01 (1.50) - renamed ImDrawList::PathFill() (rarely used directly) to ImDrawList::PathFillConvex() for
1259clarity.
1260 - 2016/11/06 (1.50) - BeginChild(const char*) now applies the stack id to the provided label, consistently with other
1261functions as it should always have been. It shouldn't affect you unless (extremely unlikely) you were appending multiple
1262times to a same child from different locations of the stack id. If that's the case, generate an id with GetID() and use
1263it instead of passing string to BeginChild().
1264 - 2016/10/15 (1.50) - avoid 'void* user_data' parameter to io.SetClipboardTextFn/io.GetClipboardTextFn pointers. We
1265pass io.ClipboardUserData to it.
1266 - 2016/09/25 (1.50) - style.WindowTitleAlign is now a ImVec2 (ImGuiAlign enum was removed). set to (0.5f,0.5f) for
1267horizontal+vertical centering, (0.0f,0.0f) for upper-left, etc.
1268 - 2016/07/30 (1.50) - SameLine(x) with x>0.0f is now relative to left of column/group if any, and not always to left of
1269window. This was sort of always the intent and hopefully, breakage should be minimal.
1270 - 2016/05/12 (1.49) - title bar (using ImGuiCol_TitleBg/ImGuiCol_TitleBgActive colors) isn't rendered over a window
1271background (ImGuiCol_WindowBg color) anymore. If your TitleBg/TitleBgActive alpha was 1.0f or you are using the default
1272theme it will not affect you, otherwise if <1.0f you need to tweak your custom theme to readjust for the fact that we
1273don't draw a WindowBg background behind the title bar. This helper function will convert an old TitleBg/TitleBgActive
1274color into a new one with the same visual output, given the OLD color and the OLD WindowBg color: ImVec4
1275ConvertTitleBgCol(const ImVec4& win_bg_col, const ImVec4& title_bg_col) { float new_a = 1.0f - ((1.0f - win_bg_col.w) *
1276(1.0f - title_bg_col.w)), k = title_bg_col.w / new_a; return ImVec4((win_bg_col.x * win_bg_col.w + title_bg_col.x) * k,
1277(win_bg_col.y * win_bg_col.w + title_bg_col.y) * k, (win_bg_col.z * win_bg_col.w + title_bg_col.z) * k, new_a); } If
1278this is confusing, pick the RGB value from title bar from an old screenshot and apply this as TitleBg/TitleBgActive. Or
1279you may just create TitleBgActive from a tweaked TitleBg color.
1280 - 2016/05/07 (1.49) - removed confusing set of GetInternalState(), GetInternalStateSize(), SetInternalState()
1281functions. Now using CreateContext(), DestroyContext(), GetCurrentContext(), SetCurrentContext().
1282 - 2016/05/02 (1.49) - renamed SetNextTreeNodeOpened() to SetNextTreeNodeOpen(), no redirection.
1283 - 2016/05/01 (1.49) - obsoleted old signature of CollapsingHeader(const char* label, const char* str_id = NULL, bool
1284display_frame = true, bool default_open = false) as extra parameters were badly designed and rarely used. You can
1285replace the "default_open = true" flag in new API with CollapsingHeader(label, ImGuiTreeNodeFlags_DefaultOpen).
1286 - 2016/04/26 (1.49) - changed ImDrawList::PushClipRect(ImVec4 rect) to ImDrawList::PushClipRect(Imvec2 min,ImVec2
1287max,bool intersect_with_current_clip_rect=false). Note that higher-level ImGui::PushClipRect() is preferable because it
1288will clip at logic/widget level, whereas ImDrawList::PushClipRect() only affect your renderer.
1289 - 2016/04/03 (1.48) - removed style.WindowFillAlphaDefault setting which was redundant. Bake default BG alpha inside
1290style.Colors[ImGuiCol_WindowBg] and all other Bg color values. (ref GitHub issue #337).
1291 - 2016/04/03 (1.48) - renamed ImGuiCol_TooltipBg to ImGuiCol_PopupBg, used by popups/menus and tooltips. popups/menus
1292were previously using ImGuiCol_WindowBg. (ref github issue #337)
1293 - 2016/03/21 (1.48) - renamed GetWindowFont() to GetFont(), GetWindowFontSize() to GetFontSize(). Kept inline
1294redirection function (will obsolete).
1295 - 2016/03/02 (1.48) - InputText() completion/history/always callbacks: if you modify the text buffer manually (without
1296using DeleteChars()/InsertChars() helper) you need to maintain the BufTextLen field. added an assert.
1297 - 2016/01/23 (1.48) - fixed not honoring exact width passed to PushItemWidth(), previously it would add extra
1298FramePadding.x*2 over that width. if you had manual pixel-perfect alignment in place it might affect you.
1299 - 2015/12/27 (1.48) - fixed ImDrawList::AddRect() which used to render a rectangle 1 px too large on each axis.
1300 - 2015/12/04 (1.47) - renamed Color() helpers to ValueColor() - dangerously named, rarely used and probably to be made
1301obsolete.
1302 - 2015/08/29 (1.45) - with the addition of horizontal scrollbar we made various fixes to inconsistencies with dealing
1303with cursor position. GetCursorPos()/SetCursorPos() functions now include the scrolled amount. It shouldn't affect the
1304majority of users, but take note that SetCursorPosX(100.0f) puts you at +100 from the starting x position which may
1305include scrolling, not at +100 from the window left side.
1306 GetContentRegionMax()/GetWindowContentRegionMin()/GetWindowContentRegionMax() functions allow
1307include the scrolled amount. Typically those were used in cases where no scrolling would happen so it may not be a
1308problem, but watch out!
1309 - 2015/08/29 (1.45) - renamed style.ScrollbarWidth to style.ScrollbarSize
1310 - 2015/08/05 (1.44) - split imgui.cpp into extra files: imgui_demo.cpp imgui_draw.cpp imgui_internal.h that you need to
1311add to your project.
1312 - 2015/07/18 (1.44) - fixed angles in ImDrawList::PathArcTo(), PathArcToFast() (introduced in 1.43) being off by an
1313extra PI for no justifiable reason
1314 - 2015/07/14 (1.43) - add new ImFontAtlas::AddFont() API. For the old AddFont***, moved the 'font_no' parameter of
1315ImFontAtlas::AddFont** functions to the ImFontConfig structure. you need to render your textured triangles with bilinear
1316filtering to benefit from sub-pixel positioning of text.
1317 - 2015/07/08 (1.43) - switched rendering data to use indexed rendering. this is saving a fair amount of CPU/GPU and
1318enables us to get anti-aliasing for a marginal cost. this necessary change will break your rendering function! the fix
1319should be very easy. sorry for that :(
1320 - if you are using a vanilla copy of one of the imgui_impl_XXX.cpp provided in the example, you
1321just need to update your copy and you can ignore the rest.
1322 - the signature of the io.RenderDrawListsFn handler has changed!
1323 old: ImGui_XXXX_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count)
1324 new: ImGui_XXXX_RenderDrawLists(ImDrawData* draw_data).
1325 parameters: 'cmd_lists' becomes 'draw_data->CmdLists', 'cmd_lists_count' becomes
1326'draw_data->CmdListsCount' ImDrawList: 'commands' becomes 'CmdBuffer', 'vtx_buffer' becomes 'VtxBuffer', 'IdxBuffer' is
1327new. ImDrawCmd: 'vtx_count' becomes 'ElemCount', 'clip_rect' becomes 'ClipRect', 'user_callback' becomes
1328'UserCallback', 'texture_id' becomes 'TextureId'.
1329 - each ImDrawList now contains both a vertex buffer and an index buffer. For each command, render
1330ElemCount/3 triangles using indices from the index buffer.
1331 - if you REALLY cannot render indexed primitives, you can call the draw_data->DeIndexAllBuffers()
1332method to de-index the buffers. This is slow and a waste of CPU/GPU. Prefer using indexed rendering!
1333 - refer to code in the examples/ folder or ask on the GitHub if you are unsure of how to upgrade.
1334please upgrade!
1335 - 2015/07/10 (1.43) - changed SameLine() parameters from int to float.
1336 - 2015/07/02 (1.42) - renamed SetScrollPosHere() to SetScrollFromCursorPos(). Kept inline redirection function (will
1337obsolete).
1338 - 2015/07/02 (1.42) - renamed GetScrollPosY() to GetScrollY(). Necessary to reduce confusion along with other scrolling
1339functions, because positions (e.g. cursor position) are not equivalent to scrolling amount.
1340 - 2015/06/14 (1.41) - changed ImageButton() default bg_col parameter from (0,0,0,1) (black) to (0,0,0,0) (transparent)
1341- makes a difference when texture have transparence
1342 - 2015/06/14 (1.41) - changed Selectable() API from (label, selected, size) to (label, selected, flags, size). Size
1343override should have been rarely used. Sorry!
1344 - 2015/05/31 (1.40) - renamed GetWindowCollapsed() to IsWindowCollapsed() for consistency. Kept inline redirection
1345function (will obsolete).
1346 - 2015/05/31 (1.40) - renamed IsRectClipped() to IsRectVisible() for consistency. Note that return value is opposite!
1347Kept inline redirection function (will obsolete).
1348 - 2015/05/27 (1.40) - removed the third 'repeat_if_held' parameter from Button() - sorry! it was rarely used and
1349inconsistent. Use PushButtonRepeat(true) / PopButtonRepeat() to enable repeat on desired buttons.
1350 - 2015/05/11 (1.40) - changed BeginPopup() API, takes a string identifier instead of a bool. ImGui needs to manage the
1351open/closed state of popups. Call OpenPopup() to actually set the "open" state of a popup. BeginPopup() returns true if
1352the popup is opened.
1353 - 2015/05/03 (1.40) - removed style.AutoFitPadding, using style.WindowPadding makes more sense (the default values were
1354already the same).
1355 - 2015/04/13 (1.38) - renamed IsClipped() to IsRectClipped(). Kept inline redirection function until 1.50.
1356 - 2015/04/09 (1.38) - renamed ImDrawList::AddArc() to ImDrawList::AddArcFast() for compatibility with future API
1357 - 2015/04/03 (1.38) - removed ImGuiCol_CheckHovered, ImGuiCol_CheckActive, replaced with the more general
1358ImGuiCol_FrameBgHovered, ImGuiCol_FrameBgActive.
1359 - 2014/04/03 (1.38) - removed support for passing -FLT_MAX..+FLT_MAX as the range for a SliderFloat(). Use DragFloat()
1360or Inputfloat() instead.
1361 - 2015/03/17 (1.36) - renamed GetItemBoxMin()/GetItemBoxMax()/IsMouseHoveringBox() to
1362GetItemRectMin()/GetItemRectMax()/IsMouseHoveringRect(). Kept inline redirection function until 1.50.
1363 - 2015/03/15 (1.36) - renamed style.TreeNodeSpacing to style.IndentSpacing, ImGuiStyleVar_TreeNodeSpacing to
1364ImGuiStyleVar_IndentSpacing
1365 - 2015/03/13 (1.36) - renamed GetWindowIsFocused() to IsWindowFocused(). Kept inline redirection function until 1.50.
1366 - 2015/03/08 (1.35) - renamed style.ScrollBarWidth to style.ScrollbarWidth (casing)
1367 - 2015/02/27 (1.34) - renamed OpenNextNode(bool) to SetNextTreeNodeOpened(bool, ImGuiSetCond). Kept inline redirection
1368function until 1.50.
1369 - 2015/02/27 (1.34) - renamed ImGuiSetCondition_*** to ImGuiSetCond_***, and _FirstUseThisSession becomes _Once.
1370 - 2015/02/11 (1.32) - changed text input callback ImGuiTextEditCallback return type from void-->int. reserved for
1371future use, return 0 for now.
1372 - 2015/02/10 (1.32) - renamed GetItemWidth() to CalcItemWidth() to clarify its evolving behavior
1373 - 2015/02/08 (1.31) - renamed GetTextLineSpacing() to GetTextLineHeightWithSpacing()
1374 - 2015/02/01 (1.31) - removed IO.MemReallocFn (unused)
1375 - 2015/01/19 (1.30) - renamed ImGuiStorage::GetIntPtr()/GetFloatPtr() to GetIntRef()/GetIntRef() because Ptr was
1376conflicting with actual pointer storage functions.
1377 - 2015/01/11 (1.30) - big font/image API change! now loads TTF file. allow for multiple fonts. no need for a PNG
1378loader.
1379 - 2015/01/11 (1.30) - removed GetDefaultFontData(). uses io.Fonts->GetTextureData*() API to retrieve uncompressed
1380pixels.
1381 - old: const void* png_data; unsigned int png_size; ImGui::GetDefaultFontData(NULL, NULL,
1382&png_data, &png_size); [..Upload texture to GPU..];
1383 - new: unsigned char* pixels; int width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width,
1384&height); [..Upload texture to GPU..]; io.Fonts->SetTexID(YourTexIdentifier); you now have more flexibility to load
1385multiple TTF fonts and manage the texture buffer for internal needs. It is now recommended that you sample the font
1386texture with bilinear interpolation.
1387 - 2015/01/11 (1.30) - added texture identifier in ImDrawCmd passed to your render function (we can now render images).
1388make sure to call io.Fonts->SetTexID()
1389 - 2015/01/11 (1.30) - removed IO.PixelCenterOffset (unnecessary, can be handled in user projection matrix)
1390 - 2015/01/11 (1.30) - removed ImGui::IsItemFocused() in favor of ImGui::IsItemActive() which handles all widgets
1391 - 2014/12/10 (1.18) - removed SetNewWindowDefaultPos() in favor of new generic API SetNextWindowPos(pos,
1392ImGuiSetCondition_FirstUseEver)
1393 - 2014/11/28 (1.17) - moved IO.Font*** options to inside the IO.Font-> structure (FontYOffset, FontTexUvForWhite,
1394FontBaseScale, FontFallbackGlyph)
1395 - 2014/11/26 (1.17) - reworked syntax of IMGUI_ONCE_UPON_A_FRAME helper macro to increase compiler compatibility
1396 - 2014/11/07 (1.15) - renamed IsHovered() to IsItemHovered()
1397 - 2014/10/02 (1.14) - renamed IMGUI_INCLUDE_IMGUI_USER_CPP to IMGUI_INCLUDE_IMGUI_USER_INL and imgui_user.cpp to
1398imgui_user.inl (more IDE friendly)
1399 - 2014/09/25 (1.13) - removed 'text_end' parameter from IO.SetClipboardTextFn (the string is now always zero-terminated
1400for simplicity)
1401 - 2014/09/24 (1.12) - renamed SetFontScale() to SetWindowFontScale()
1402 - 2014/09/24 (1.12) - moved IM_MALLOC/IM_REALLOC/IM_FREE preprocessor defines to
1403IO.MemAllocFn/IO.MemReallocFn/IO.MemFreeFn
1404 - 2014/08/30 (1.09) - removed IO.FontHeight (now computed automatically)
1405 - 2014/08/30 (1.09) - moved IMGUI_FONT_TEX_UV_FOR_WHITE preprocessor define to IO.FontTexUvForWhite
1406 - 2014/08/28 (1.09) - changed the behavior of IO.PixelCenterOffset following various rendering fixes
1407
1408
1409 FREQUENTLY ASKED QUESTIONS (FAQ)
1410 ================================
1411
1412 Read all answers online:
1413 https://www.dearimgui.com/faq or https://github.com/ocornut/imgui/blob/master/docs/FAQ.md (same url)
1414 Read all answers locally (with a text editor or ideally a Markdown viewer):
1415 docs/FAQ.md
1416 Some answers are copied down here to facilitate searching in code.
1417
1418 Q&A: Basics
1419 ===========
1420
1421 Q: Where is the documentation?
1422 A: This library is poorly documented at the moment and expects the user to be acquainted with C/C++.
1423 - Run the examples/ applications and explore them.
1424 - Read Getting Started (https://github.com/ocornut/imgui/wiki/Getting-Started) guide.
1425 - See demo code in imgui_demo.cpp and particularly the ImGui::ShowDemoWindow() function.
1426 - The demo covers most features of Dear ImGui, so you can read the code and see its output.
1427 - See documentation and comments at the top of imgui.cpp + effectively imgui.h.
1428 - 20+ standalone example applications using e.g. OpenGL/DirectX are provided in the
1429 examples/ folder to explain how to integrate Dear ImGui with your own engine/application.
1430 - The Wiki (https://github.com/ocornut/imgui/wiki) has many resources and links.
1431 - The Glossary (https://github.com/ocornut/imgui/wiki/Glossary) page also may be useful.
1432 - Your programming IDE is your friend, find the type or function declaration to find comments
1433 associated with it.
1434
1435 Q: What is this library called?
1436 Q: Which version should I get?
1437 >> This library is called "Dear ImGui", please don't call it "ImGui" :)
1438 >> See https://www.dearimgui.com/faq for details.
1439
1440 Q&A: Integration
1441 ================
1442
1443 Q: How to get started?
1444 A: Read https://github.com/ocornut/imgui/wiki/Getting-Started. Read 'PROGRAMMER GUIDE' above. Read examples/README.txt.
1445
1446 Q: How can I tell whether to dispatch mouse/keyboard to Dear ImGui or my application?
1447 A: You should read the 'io.WantCaptureMouse', 'io.WantCaptureKeyboard' and 'io.WantTextInput' flags!
1448 >> See https://www.dearimgui.com/faq for a fully detailed answer. You really want to read this.
1449
1450 Q. How can I enable keyboard or gamepad controls?
1451 Q: How can I use this on a machine without mouse, keyboard or screen? (input share, remote display)
1452 Q: I integrated Dear ImGui in my engine and little squares are showing instead of text...
1453 Q: I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around...
1454 Q: I integrated Dear ImGui in my engine and some elements are displaying outside their expected windows boundaries...
1455 >> See https://www.dearimgui.com/faq
1456
1457 Q&A: Usage
1458 ----------
1459
1460 Q: About the ID Stack system..
1461 - Why is my widget not reacting when I click on it?
1462 - How can I have widgets with an empty label?
1463 - How can I have multiple widgets with the same label?
1464 - How can I have multiple windows with the same label?
1465 Q: How can I display an image? What is ImTextureID, how does it work?
1466 Q: How can I use my own math types instead of ImVec2?
1467 Q: How can I interact with standard C++ types (such as std::string and std::vector)?
1468 Q: How can I display custom shapes? (using low-level ImDrawList API)
1469 >> See https://www.dearimgui.com/faq
1470
1471 Q&A: Fonts, Text
1472 ================
1473
1474 Q: How should I handle DPI in my application?
1475 Q: How can I load a different font than the default?
1476 Q: How can I easily use icons in my application?
1477 Q: How can I load multiple fonts?
1478 Q: How can I display and input non-Latin characters such as Chinese, Japanese, Korean, Cyrillic?
1479 >> See https://www.dearimgui.com/faq and https://github.com/ocornut/imgui/blob/master/docs/FONTS.md
1480
1481 Q&A: Concerns
1482 =============
1483
1484 Q: Who uses Dear ImGui?
1485 Q: Can you create elaborate/serious tools with Dear ImGui?
1486 Q: Can you reskin the look of Dear ImGui?
1487 Q: Why using C++ (as opposed to C)?
1488 >> See https://www.dearimgui.com/faq
1489
1490 Q&A: Community
1491 ==============
1492
1493 Q: How can I help?
1494 A: - Businesses: please reach out to "omar AT dearimgui DOT com" if you work in a place using Dear ImGui!
1495 We can discuss ways for your company to fund development via invoiced technical support, maintenance or sponsoring
1496contacts. This is among the most useful thing you can do for Dear ImGui. With increased funding, we sustain and grow
1497work on this project.
1498 >>> See https://github.com/ocornut/imgui/wiki/Funding
1499 - Businesses: you can also purchase licenses for the Dear ImGui Automation/Test Engine.
1500 - If you are experienced with Dear ImGui and C++, look at the GitHub issues, look at the Wiki, and see how you want
1501to help and can help!
1502 - Disclose your usage of Dear ImGui via a dev blog post, a tweet, a screenshot, a mention somewhere etc.
1503 You may post screenshot or links in the gallery threads. Visuals are ideal as they inspire other programmers.
1504 But even without visuals, disclosing your use of dear imgui helps the library grow credibility, and help other
1505teams and programmers with taking decisions.
1506 - If you have issues or if you need to hack into the library, even if you don't expect any support it is useful that
1507you share your issues (on GitHub or privately).
1508
1509*/
1510
1511//-------------------------------------------------------------------------
1512// [SECTION] INCLUDES
1513//-------------------------------------------------------------------------
1514
1515#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
1516#define _CRT_SECURE_NO_WARNINGS
1517#endif
1518
1519#ifndef IMGUI_DEFINE_MATH_OPERATORS
1520#define IMGUI_DEFINE_MATH_OPERATORS
1521#endif
1522
1523#include "imgui.h"
1524#ifndef IMGUI_DISABLE
1525#include "imgui_internal.h"
1526
1527// System includes
1528#include <stdio.h> // vsnprintf, sscanf, printf
1529#include <stdint.h> // intptr_t
1530
1531// [Windows] On non-Visual Studio compilers, we default to IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS unless explicitly
1532// enabled
1533#if defined(_WIN32) && !defined(_MSC_VER) && !defined(IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS) && \
1534 !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS)
1535#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS
1536#endif
1537
1538// [Windows] OS specific includes (optional)
1539#if defined(_WIN32) && defined(IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS) && \
1540 defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS) && defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS) && \
1541 defined(IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS)
1542#define IMGUI_DISABLE_WIN32_FUNCTIONS
1543#endif
1544#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS)
1545#ifndef WIN32_LEAN_AND_MEAN
1546#define WIN32_LEAN_AND_MEAN
1547#endif
1548#ifndef NOMINMAX
1549#define NOMINMAX
1550#endif
1551#ifndef __MINGW32__
1552#include <Windows.h> // _wfopen, OpenClipboard
1553#else
1554#include <windows.h>
1555#endif
1556#if defined(WINAPI_FAMILY) && ((defined(WINAPI_FAMILY_APP) && WINAPI_FAMILY == WINAPI_FAMILY_APP) || \
1557 (defined(WINAPI_FAMILY_GAMES) && WINAPI_FAMILY == WINAPI_FAMILY_GAMES))
1558// The UWP and GDK Win32 API subsets don't support clipboard nor IME functions
1559#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS
1560#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS
1561#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS
1562#endif
1563#endif
1564
1565// [Apple] OS specific includes
1566#if defined(__APPLE__)
1567#include <TargetConditionals.h>
1568#endif
1569
1570// Visual Studio warnings
1571#ifdef _MSC_VER
1572#pragma warning(disable : 4127) // condition expression is constant
1573#pragma warning( \
1574 disable : 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen
1575#if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later
1576#pragma warning(disable : 5054) // operator '|': deprecated between enumerations of different types
1577#endif
1578#pragma warning(disable : 26451) // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and
1579 // then casting the result to an 8 byte value. Cast the value to the wider type before
1580 // calling operator 'xxx' to avoid overflow(io.2).
1581#pragma warning( \
1582 disable \
1583 : 26495) // [Static Analyzer] Variable 'XXX' is uninitialized. Always initialize a member variable (type.6).
1584#pragma warning( \
1585 disable : 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3).
1586#endif
1587
1588// Clang/GCC warnings with -Weverything
1589#if defined(__clang__)
1590#if __has_warning("-Wunknown-warning-option")
1591#pragma clang diagnostic ignored \
1592 "-Wunknown-warning-option" // warning: unknown warning group 'xxx' // not all warnings are
1593 // known by all Clang versions and they tend to be rename-happy.. so ignoring warnings
1594 // triggers new warnings on some configuration. Great!
1595#endif
1596#pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx'
1597#pragma clang diagnostic ignored \
1598 "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse.
1599#pragma clang diagnostic ignored "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe // storing
1600 // and comparing against same constants (typically 0.0f) is ok.
1601#pragma clang diagnostic ignored \
1602 "-Wformat" // warning: format specifies type 'int' but the argument has type 'unsigned int'
1603#pragma clang diagnostic ignored \
1604 "-Wformat-nonliteral" // warning: format string is not a string literal // passing non-literal to
1605 // vsnformat(). yes, user passing incorrect format strings can crash the code.
1606#pragma clang diagnostic ignored \
1607 "-Wformat-pedantic" // warning: format specifies type 'void *' but the argument has type 'xxxx *' // unreasonable,
1608 // would lead to casting every %p arg to void*. probably enabled by -pedantic.
1609#pragma clang diagnostic ignored \
1610 "-Wexit-time-destructors" // warning: declaration requires an exit-time destructor // exit-time destruction
1611 // order is undefined. if MemFree() leads to users code that has been disabled before exit
1612 // it might cause problems. ImGui coding style welcomes static/globals.
1613#pragma clang diagnostic ignored "-Wglobal-constructors" // warning: declaration requires a global destructor //
1614 // similar to above, not sure what the exact difference is.
1615#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness
1616#pragma clang diagnostic ignored \
1617 "-Wint-to-void-pointer-cast" // warning: cast to 'void *' from smaller integer type 'int'
1618#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant // some
1619 // standard header variations use #define NULL 0
1620#pragma clang diagnostic ignored \
1621 "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function //
1622 // using printf() is a misery with this as C++ va_arg ellipsis changes float to double.
1623#pragma clang diagnostic ignored \
1624 "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision
1625#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access
1626#pragma clang diagnostic ignored "-Wnontrivial-memaccess" // warning: first argument in call to 'memset' is a pointer to
1627 // non-trivially copyable type
1628#pragma clang diagnostic ignored "-Wswitch-default" // warning: 'switch' missing 'default' label
1629#elif defined(__GNUC__)
1630// We disable -Wpragmas because GCC doesn't provide a has_warning equivalent and some forks/patches may not follow the
1631// warning/version association.
1632#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind
1633#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used
1634#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size
1635#pragma GCC diagnostic ignored "-Wfloat-equal" // warning: comparing floating-point with '==' or '!=' is unsafe
1636#pragma GCC diagnostic ignored "-Wformat" // warning: format '%p' expects argument of type 'int'/'void*', but argument X
1637 // has type 'unsigned int'/'ImGuiWindow*'
1638#pragma GCC diagnostic ignored \
1639 "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function
1640#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value
1641#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked
1642#pragma GCC diagnostic ignored "-Wstrict-overflow" // warning: assuming signed overflow does not occur when assuming
1643 // that (X - c) > X is always false
1644#pragma GCC diagnostic ignored \
1645 "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no
1646 // trivial copy-assignment; use assignment or value-initialization instead
1647#pragma GCC diagnostic ignored \
1648 "-Wcast-qual" // warning: cast from type 'const xxxx *' to type 'xxxx *' casts away qualifiers
1649#endif
1650
1651// Debug options
1652#define IMGUI_DEBUG_NAV_SCORING \
1653 0 // Display navigation scoring preview when hovering items. Hold CTRL to display for all candidates. CTRL+Arrow to
1654 // change last direction.
1655#define IMGUI_DEBUG_NAV_RECTS 0 // Display the reference navigation rectangle for each window
1656
1657// When using CTRL+TAB (or Gamepad Square+L/R) we delay the visual a little in order to reduce visual noise doing a fast
1658// switch.
1659static const float NAV_WINDOWING_HIGHLIGHT_DELAY =
1660 0.20f; // Time before the highlight and screen dimming starts fading in
1661static const float NAV_WINDOWING_LIST_APPEAR_DELAY = 0.15f; // Time before the window list starts to appear
1662static const float NAV_ACTIVATE_HIGHLIGHT_TIMER = 0.10f; // Time to highlight an item activated by a shortcut.
1663static const float WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER =
1664 0.04f; // Reduce visual noise by only highlighting the border after a certain time.
1665static const float WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER =
1666 0.70f; // Lock scrolled window (so it doesn't pick child windows that are scrolling through) for a certain time,
1667 // unless mouse moved.
1668
1669// Tooltip offset
1670static const ImVec2 TOOLTIP_DEFAULT_OFFSET_MOUSE = ImVec2(16, 10); // Multiplied by g.Style.MouseCursorScale
1671static const ImVec2 TOOLTIP_DEFAULT_OFFSET_TOUCH = ImVec2(0, -20); // Multiplied by g.Style.MouseCursorScale
1672static const ImVec2 TOOLTIP_DEFAULT_PIVOT_TOUCH = ImVec2(0.5f, 1.0f); // Multiplied by g.Style.MouseCursorScale
1673
1674// Docking
1675static const float DOCKING_TRANSPARENT_PAYLOAD_ALPHA =
1676 0.50f; // For use with io.ConfigDockingTransparentPayload. Apply to Viewport _or_ WindowBg in host viewport.
1677
1678//-------------------------------------------------------------------------
1679// [SECTION] FORWARD DECLARATIONS
1680//-------------------------------------------------------------------------
1681
1682static void SetCurrentWindow(ImGuiWindow *window);
1683static ImGuiWindow *CreateNewWindow(const char *name, ImGuiWindowFlags flags);
1684static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow *window);
1685
1686static void AddWindowToSortBuffer(ImVector<ImGuiWindow *> *out_sorted_windows, ImGuiWindow *window);
1687
1688// Settings
1689static void WindowSettingsHandler_ClearAll(ImGuiContext *, ImGuiSettingsHandler *);
1690static void *WindowSettingsHandler_ReadOpen(ImGuiContext *, ImGuiSettingsHandler *, const char *name);
1691static void WindowSettingsHandler_ReadLine(ImGuiContext *, ImGuiSettingsHandler *, void *entry, const char *line);
1692static void WindowSettingsHandler_ApplyAll(ImGuiContext *, ImGuiSettingsHandler *);
1693static void WindowSettingsHandler_WriteAll(ImGuiContext *, ImGuiSettingsHandler *, ImGuiTextBuffer *buf);
1694
1695// Platform Dependents default implementation for ImGuiPlatformIO functions
1696static const char *Platform_GetClipboardTextFn_DefaultImpl(ImGuiContext *ctx);
1697static void Platform_SetClipboardTextFn_DefaultImpl(ImGuiContext *ctx, const char *text);
1698static void Platform_SetImeDataFn_DefaultImpl(ImGuiContext *ctx, ImGuiViewport *viewport, ImGuiPlatformImeData *data);
1699static bool Platform_OpenInShellFn_DefaultImpl(ImGuiContext *ctx, const char *path);
1700
1701namespace ImGui
1702{
1703// Item
1704static void ItemHandleShortcut(ImGuiID id);
1705
1706// Window Focus
1707static int FindWindowFocusIndex(ImGuiWindow *window);
1708static void UpdateWindowInFocusOrderList(ImGuiWindow *window, bool just_created, ImGuiWindowFlags new_flags);
1709
1710// Navigation
1711static void NavUpdate();
1712static void NavUpdateWindowing();
1713static void NavUpdateWindowingApplyFocus(ImGuiWindow *window);
1714static void NavUpdateWindowingOverlay();
1715static void NavUpdateCancelRequest();
1716static void NavUpdateCreateMoveRequest();
1717static void NavUpdateCreateTabbingRequest();
1718static float NavUpdatePageUpPageDown();
1719static inline void NavUpdateAnyRequestFlag();
1720static void NavUpdateCreateWrappingRequest();
1721static void NavEndFrame();
1722static bool NavScoreItem(ImGuiNavItemData *result);
1723static void NavApplyItemToResult(ImGuiNavItemData *result);
1724static void NavProcessItem();
1725static void NavProcessItemForTabbingRequest(ImGuiID id, ImGuiItemFlags item_flags, ImGuiNavMoveFlags move_flags);
1726static ImGuiInputSource NavCalcPreferredRefPosSource();
1727static ImVec2 NavCalcPreferredRefPos();
1728static void NavSaveLastChildNavWindowIntoParent(ImGuiWindow *nav_window);
1729static ImGuiWindow *NavRestoreLastChildNavWindow(ImGuiWindow *window);
1730static void NavRestoreLayer(ImGuiNavLayer layer);
1731
1732// Error Checking and Debug Tools
1733static void ErrorCheckNewFrameSanityChecks();
1734static void ErrorCheckEndFrameSanityChecks();
1735#ifndef IMGUI_DISABLE_DEBUG_TOOLS
1736static void UpdateDebugToolItemPicker();
1737static void UpdateDebugToolStackQueries();
1738static void UpdateDebugToolFlashStyleColor();
1739#endif
1740
1741// Inputs
1742static void UpdateKeyboardInputs();
1743static void UpdateMouseInputs();
1744static void UpdateMouseWheel();
1745static void UpdateKeyRoutingTable(ImGuiKeyRoutingTable *rt);
1746
1747// Misc
1748static void UpdateSettings();
1749static int UpdateWindowManualResize(ImGuiWindow *window, const ImVec2 &size_auto_fit, int *border_hovered,
1750 int *border_held, int resize_grip_count, ImU32 resize_grip_col[4],
1751 const ImRect &visibility_rect);
1752static void RenderWindowOuterBorders(ImGuiWindow *window);
1753static void RenderWindowDecorations(ImGuiWindow *window, const ImRect &title_bar_rect, bool title_bar_is_highlight,
1754 bool handle_borders_and_resize_grips, int resize_grip_count,
1755 const ImU32 resize_grip_col[4], float resize_grip_draw_size);
1756static void RenderWindowTitleBarContents(ImGuiWindow *window, const ImRect &title_bar_rect, const char *name,
1757 bool *p_open);
1758static void RenderDimmedBackgroundBehindWindow(ImGuiWindow *window, ImU32 col);
1759static void RenderDimmedBackgrounds();
1760static void SetLastItemDataForWindow(ImGuiWindow *window, const ImRect &rect);
1761static void SetLastItemDataForChildWindowItem(ImGuiWindow *window, const ImRect &rect);
1762
1763// Viewports
1764const ImGuiID IMGUI_VIEWPORT_DEFAULT_ID =
1765 0x11111111; // Using an arbitrary constant instead of e.g. ImHashStr("ViewportDefault", 0); so it's easier to spot
1766 // in the debugger. The exact value doesn't matter.
1767static ImGuiViewportP *AddUpdateViewport(ImGuiWindow *window, ImGuiID id, const ImVec2 &platform_pos,
1768 const ImVec2 &size, ImGuiViewportFlags flags);
1769static void DestroyViewport(ImGuiViewportP *viewport);
1770static void UpdateViewportsNewFrame();
1771static void UpdateViewportsEndFrame();
1772static void WindowSelectViewport(ImGuiWindow *window);
1773static void WindowSyncOwnedViewport(ImGuiWindow *window, ImGuiWindow *parent_window_in_stack);
1774static bool UpdateTryMergeWindowIntoHostViewport(ImGuiWindow *window, ImGuiViewportP *host_viewport);
1775static bool UpdateTryMergeWindowIntoHostViewports(ImGuiWindow *window);
1776static bool GetWindowAlwaysWantOwnViewport(ImGuiWindow *window);
1777static int FindPlatformMonitorForPos(const ImVec2 &pos);
1778static int FindPlatformMonitorForRect(const ImRect &r);
1779static void UpdateViewportPlatformMonitor(ImGuiViewportP *viewport);
1780
1781} // namespace ImGui
1782
1783//-----------------------------------------------------------------------------
1784// [SECTION] CONTEXT AND MEMORY ALLOCATORS
1785//-----------------------------------------------------------------------------
1786
1787// DLL users:
1788// - Heaps and globals are not shared across DLL boundaries!
1789// - You will need to call SetCurrentContext() + SetAllocatorFunctions() for each static/DLL boundary you are calling
1790// from.
1791// - Same applies for hot-reloading mechanisms that are reliant on reloading DLL (note that many hot-reloading
1792// mechanisms work without DLL).
1793// - Using Dear ImGui via a shared library is not recommended, because of function call overhead and because we don't
1794// guarantee backward nor forward ABI compatibility.
1795// - Confused? In a debugger: add GImGui to your watch window and notice how its value changes depending on your current
1796// location (which DLL boundary you are in).
1797
1798// Current context pointer. Implicitly used by all Dear ImGui functions. Always assumed to be != NULL.
1799// - ImGui::CreateContext() will automatically set this pointer if it is NULL.
1800// Change to a different context by calling ImGui::SetCurrentContext().
1801// - Important: Dear ImGui functions are not thread-safe because of this pointer.
1802// If you want thread-safety to allow N threads to access N different contexts:
1803// - Change this variable to use thread local storage so each thread can refer to a different context, in your
1804// imconfig.h:
1805// struct ImGuiContext;
1806// extern thread_local ImGuiContext* MyImGuiTLS;
1807// #define GImGui MyImGuiTLS
1808// And then define MyImGuiTLS in one of your cpp files. Note that thread_local is a C++11 keyword, earlier C++ uses
1809// compiler-specific keyword.
1810// - Future development aims to make this context pointer explicit to all calls. Also read
1811// https://github.com/ocornut/imgui/issues/586
1812// - If you need a finite number of contexts, you may compile and use multiple instances of the ImGui code from a
1813// different namespace.
1814// - DLL users: read comments above.
1815#ifndef GImGui
1816ImGuiContext *GImGui = NULL;
1817#endif
1818
1819// Memory Allocator functions. Use SetAllocatorFunctions() to change them.
1820// - You probably don't want to modify that mid-program, and if you use global/static e.g. ImVector<> instances you may
1821// need to keep them accessible during program destruction.
1822// - DLL users: read comments above.
1823#ifndef IMGUI_DISABLE_DEFAULT_ALLOCATORS
1824static void *MallocWrapper(size_t size, void *user_data)
1825{
1826 IM_UNUSED(user_data);
1827 return malloc(size);
1828}
1829static void FreeWrapper(void *ptr, void *user_data)
1830{
1831 IM_UNUSED(user_data);
1832 free(ptr);
1833}
1834#else
1835static void *MallocWrapper(size_t size, void *user_data)
1836{
1837 IM_UNUSED(user_data);
1838 IM_UNUSED(size);
1839 IM_ASSERT(0);
1840 return NULL;
1841}
1842static void FreeWrapper(void *ptr, void *user_data)
1843{
1844 IM_UNUSED(user_data);
1845 IM_UNUSED(ptr);
1846 IM_ASSERT(0);
1847}
1848#endif
1849static ImGuiMemAllocFunc GImAllocatorAllocFunc = MallocWrapper;
1850static ImGuiMemFreeFunc GImAllocatorFreeFunc = FreeWrapper;
1851static void *GImAllocatorUserData = NULL;
1852
1853//-----------------------------------------------------------------------------
1854// [SECTION] USER FACING STRUCTURES (ImGuiStyle, ImGuiIO, ImGuiPlatformIO)
1855//-----------------------------------------------------------------------------
1856
1857ImGuiStyle::ImGuiStyle()
1858{
1859 Alpha = 1.0f; // Global alpha applies to everything in Dear ImGui.
1860 DisabledAlpha =
1861 0.60f; // Additional alpha multiplier applied by BeginDisabled(). Multiply over current value of Alpha.
1862 WindowPadding = ImVec2(8, 8); // Padding within a window
1863 WindowRounding = 0.0f; // Radius of window corners rounding. Set to 0.0f to have rectangular windows. Large values
1864 // tend to lead to variety of artifacts and are not recommended.
1865 WindowBorderSize =
1866 1.0f; // Thickness of border around windows. Generally set to 0.0f or 1.0f. Other values not well tested.
1867 WindowBorderHoverPadding =
1868 4.0f; // Hit-testing extent outside/inside resizing border. Also extend determination of hovered window.
1869 // Generally meaningfully larger than WindowBorderSize to make it easy to reach borders.
1870 WindowMinSize = ImVec2(32, 32); // Minimum window size
1871 WindowTitleAlign = ImVec2(0.0f, 0.5f); // Alignment for title bar text
1872 WindowMenuButtonPosition = ImGuiDir_Left; // Position of the collapsing/docking button in the title bar
1873 // (left/right). Defaults to ImGuiDir_Left.
1874 ChildRounding = 0.0f; // Radius of child window corners rounding. Set to 0.0f to have rectangular child windows
1875 ChildBorderSize =
1876 1.0f; // Thickness of border around child windows. Generally set to 0.0f or 1.0f. Other values not well tested.
1877 PopupRounding = 0.0f; // Radius of popup window corners rounding. Set to 0.0f to have rectangular child windows
1878 PopupBorderSize = 1.0f; // Thickness of border around popup or tooltip windows. Generally set to 0.0f or 1.0f. Other
1879 // values not well tested.
1880 FramePadding = ImVec2(4, 3); // Padding within a framed rectangle (used by most widgets)
1881 FrameRounding =
1882 0.0f; // Radius of frame corners rounding. Set to 0.0f to have rectangular frames (used by most widgets).
1883 FrameBorderSize =
1884 0.0f; // Thickness of border around frames. Generally set to 0.0f or 1.0f. Other values not well tested.
1885 ItemSpacing = ImVec2(8, 4); // Horizontal and vertical spacing between widgets/lines
1886 ItemInnerSpacing = ImVec2(4, 4); // Horizontal and vertical spacing between within elements of a composed widget
1887 // (e.g. a slider and its label)
1888 CellPadding = ImVec2(4, 2); // Padding within a table cell. Cellpadding.x is locked for entire table. CellPadding.y
1889 // may be altered between different rows.
1890 TouchExtraPadding = ImVec2(0, 0); // Expand reactive bounding box for touch-based system where touch position is not
1891 // accurate enough. Unfortunately we don't sort widgets so priority on overlap
1892 // will always be given to the first widget. So don't grow this too much!
1893 IndentSpacing =
1894 21.0f; // Horizontal spacing when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2).
1895 ColumnsMinSpacing = 6.0f; // Minimum horizontal spacing between two columns. Preferably > (FramePadding.x + 1).
1896 ScrollbarSize = 14.0f; // Width of the vertical scrollbar, Height of the horizontal scrollbar
1897 ScrollbarRounding = 9.0f; // Radius of grab corners rounding for scrollbar
1898 GrabMinSize = 12.0f; // Minimum width/height of a grab box for slider/scrollbar
1899 GrabRounding = 0.0f; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs.
1900 LogSliderDeadzone = 4.0f; // The size in pixels of the dead-zone around zero on logarithmic sliders that cross zero.
1901 ImageBorderSize = 0.0f; // Thickness of border around tabs.
1902 TabRounding = 5.0f; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs.
1903 TabBorderSize = 0.0f; // Thickness of border around tabs.
1904 TabCloseButtonMinWidthSelected =
1905 -1.0f; // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width.
1906 TabCloseButtonMinWidthUnselected =
1907 0.0f; // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width. FLT_MAX:
1908 // never show close button when unselected.
1909 TabBarBorderSize = 1.0f; // Thickness of tab-bar separator, which takes on the tab active color to denote focus.
1910 TabBarOverlineSize = 1.0f; // Thickness of tab-bar overline, which highlights the selected tab-bar.
1911 TableAngledHeadersAngle =
1912 35.0f * (IM_PI / 180.0f); // Angle of angled headers (supported values range from -50 degrees to +50 degrees).
1913 TableAngledHeadersTextAlign = ImVec2(0.5f, 0.0f); // Alignment of angled headers within the cell
1914 ColorButtonPosition =
1915 ImGuiDir_Right; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right.
1916 ButtonTextAlign = ImVec2(0.5f, 0.5f); // Alignment of button text when button is larger than text.
1917 SelectableTextAlign =
1918 ImVec2(0.0f, 0.0f); // Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally
1919 // important to keep this left-aligned if you want to lay multiple items on a same line.
1920 SeparatorTextBorderSize = 3.0f; // Thickness of border in SeparatorText()
1921 SeparatorTextAlign =
1922 ImVec2(0.0f, 0.5f); // Alignment of text within the separator. Defaults to (0.0f, 0.5f) (left aligned, center).
1923 SeparatorTextPadding =
1924 ImVec2(20.0f, 3.f); // Horizontal offset of text from each edge of the separator + spacing on other axis.
1925 // Generally small values. .y is recommended to be == FramePadding.y.
1926 DisplayWindowPadding = ImVec2(19, 19); // Window position are clamped to be visible within the display area or
1927 // monitors by at least this amount. Only applies to regular windows.
1928 DisplaySafeAreaPadding = ImVec2(3, 3); // If you cannot see the edge of your screen (e.g. on a TV) increase the safe
1929 // area padding. Covers popups/tooltips as well regular windows.
1930 DockingSeparatorSize = 2.0f; // Thickness of resizing border between docked windows
1931 MouseCursorScale =
1932 1.0f; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later.
1933 AntiAliasedLines = true; // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU.
1934 AntiAliasedLinesUseTex = true; // Enable anti-aliased lines/borders using textures where possible. Require backend
1935 // to render with bilinear filtering (NOT point/nearest filtering).
1936 AntiAliasedFill = true; // Enable anti-aliased filled shapes (rounded rectangles, circles, etc.).
1937 CurveTessellationTol =
1938 1.25f; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease
1939 // for highly tessellated curves (higher quality, more polygons), increase to reduce quality.
1940 CircleTessellationMaxError =
1941 0.30f; // Maximum error (in pixels) allowed when using AddCircle()/AddCircleFilled() or drawing rounded corner
1942 // rectangles with no explicit segment count specified. Decrease for higher quality but more geometry.
1943
1944 // Behaviors
1945 HoverStationaryDelay =
1946 0.15f; // Delay for IsItemHovered(ImGuiHoveredFlags_Stationary). Time required to consider mouse stationary.
1947 HoverDelayShort =
1948 0.15f; // Delay for IsItemHovered(ImGuiHoveredFlags_DelayShort). Usually used along with HoverStationaryDelay.
1949 HoverDelayNormal = 0.40f; // Delay for IsItemHovered(ImGuiHoveredFlags_DelayNormal). "
1950 HoverFlagsForTooltipMouse =
1951 ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayShort |
1952 ImGuiHoveredFlags_AllowWhenDisabled; // Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or
1953 // BeginItemTooltip()/SetItemTooltip() while using mouse.
1954 HoverFlagsForTooltipNav =
1955 ImGuiHoveredFlags_NoSharedDelay | ImGuiHoveredFlags_DelayNormal |
1956 ImGuiHoveredFlags_AllowWhenDisabled; // Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or
1957 // BeginItemTooltip()/SetItemTooltip() while using keyboard/gamepad.
1958
1959 // Default theme
1960 ImGui::StyleColorsDark(this);
1961}
1962
1963// To scale your entire UI (e.g. if you want your app to use High DPI or generally be DPI aware) you may use this helper
1964// function. Scaling the fonts is done separately and is up to you. Important: This operation is lossy because we round
1965// all sizes to integer. If you need to change your scale multiples, call this over a freshly initialized ImGuiStyle
1966// structure rather than scaling multiple times.
1967void ImGuiStyle::ScaleAllSizes(float scale_factor)
1968{
1969 WindowPadding = ImTrunc(WindowPadding * scale_factor);
1970 WindowRounding = ImTrunc(WindowRounding * scale_factor);
1971 WindowMinSize = ImTrunc(WindowMinSize * scale_factor);
1972 WindowBorderHoverPadding = ImTrunc(WindowBorderHoverPadding * scale_factor);
1973 ChildRounding = ImTrunc(ChildRounding * scale_factor);
1974 PopupRounding = ImTrunc(PopupRounding * scale_factor);
1975 FramePadding = ImTrunc(FramePadding * scale_factor);
1976 FrameRounding = ImTrunc(FrameRounding * scale_factor);
1977 ItemSpacing = ImTrunc(ItemSpacing * scale_factor);
1978 ItemInnerSpacing = ImTrunc(ItemInnerSpacing * scale_factor);
1979 CellPadding = ImTrunc(CellPadding * scale_factor);
1980 TouchExtraPadding = ImTrunc(TouchExtraPadding * scale_factor);
1981 IndentSpacing = ImTrunc(IndentSpacing * scale_factor);
1982 ColumnsMinSpacing = ImTrunc(ColumnsMinSpacing * scale_factor);
1983 ScrollbarSize = ImTrunc(ScrollbarSize * scale_factor);
1984 ScrollbarRounding = ImTrunc(ScrollbarRounding * scale_factor);
1985 GrabMinSize = ImTrunc(GrabMinSize * scale_factor);
1986 GrabRounding = ImTrunc(GrabRounding * scale_factor);
1987 LogSliderDeadzone = ImTrunc(LogSliderDeadzone * scale_factor);
1988 ImageBorderSize = ImTrunc(ImageBorderSize * scale_factor);
1989 TabRounding = ImTrunc(TabRounding * scale_factor);
1990 TabCloseButtonMinWidthSelected =
1991 (TabCloseButtonMinWidthSelected > 0.0f && TabCloseButtonMinWidthSelected != FLT_MAX)
1992 ? ImTrunc(TabCloseButtonMinWidthSelected * scale_factor)
1993 : TabCloseButtonMinWidthSelected;
1994 TabCloseButtonMinWidthUnselected =
1995 (TabCloseButtonMinWidthUnselected > 0.0f && TabCloseButtonMinWidthUnselected != FLT_MAX)
1996 ? ImTrunc(TabCloseButtonMinWidthUnselected * scale_factor)
1997 : TabCloseButtonMinWidthUnselected;
1998 TabBarOverlineSize = ImTrunc(TabBarOverlineSize * scale_factor);
1999 SeparatorTextPadding = ImTrunc(SeparatorTextPadding * scale_factor);
2000 DockingSeparatorSize = ImTrunc(DockingSeparatorSize * scale_factor);
2001 DisplayWindowPadding = ImTrunc(DisplayWindowPadding * scale_factor);
2002 DisplaySafeAreaPadding = ImTrunc(DisplaySafeAreaPadding * scale_factor);
2003 MouseCursorScale = ImTrunc(MouseCursorScale * scale_factor);
2004}
2005
2006ImGuiIO::ImGuiIO()
2007{
2008 // Most fields are initialized with zero
2009 memset(this, 0, sizeof(*this));
2010 IM_STATIC_ASSERT(IM_ARRAYSIZE(ImGuiIO::MouseDown) == ImGuiMouseButton_COUNT &&
2011 IM_ARRAYSIZE(ImGuiIO::MouseClicked) == ImGuiMouseButton_COUNT);
2012
2013 // Settings
2014 ConfigFlags = ImGuiConfigFlags_None;
2015 BackendFlags = ImGuiBackendFlags_None;
2016 DisplaySize = ImVec2(-1.0f, -1.0f);
2017 DeltaTime = 1.0f / 60.0f;
2018 IniSavingRate = 5.0f;
2019 IniFilename = "imgui.ini"; // Important: "imgui.ini" is relative to current working dir, most apps will want to lock
2020 // this to an absolute path (e.g. same path as executables).
2021 LogFilename = "imgui_log.txt";
2022 UserData = NULL;
2023
2024 Fonts = NULL;
2025 FontGlobalScale = 1.0f;
2026 FontDefault = NULL;
2027 FontAllowUserScaling = false;
2028 DisplayFramebufferScale = ImVec2(1.0f, 1.0f);
2029
2030 // Keyboard/Gamepad Navigation options
2031 ConfigNavSwapGamepadButtons = false;
2032 ConfigNavMoveSetMousePos = false;
2033 ConfigNavCaptureKeyboard = true;
2034 ConfigNavEscapeClearFocusItem = true;
2035 ConfigNavEscapeClearFocusWindow = false;
2036 ConfigNavCursorVisibleAuto = true;
2037 ConfigNavCursorVisibleAlways = false;
2038
2039 // Docking options (when ImGuiConfigFlags_DockingEnable is set)
2040 ConfigDockingNoSplit = false;
2041 ConfigDockingWithShift = false;
2042 ConfigDockingAlwaysTabBar = false;
2043 ConfigDockingTransparentPayload = false;
2044
2045 // Viewport options (when ImGuiConfigFlags_ViewportsEnable is set)
2046 ConfigViewportsNoAutoMerge = false;
2047 ConfigViewportsNoTaskBarIcon = false;
2048 ConfigViewportsNoDecoration = true;
2049 ConfigViewportsNoDefaultParent = false;
2050
2051 // Miscellaneous options
2052 MouseDrawCursor = false;
2053#ifdef __APPLE__
2054 ConfigMacOSXBehaviors = true; // Set Mac OS X style defaults based on __APPLE__ compile time flag
2055#else
2056 ConfigMacOSXBehaviors = false;
2057#endif
2058 ConfigInputTrickleEventQueue = true;
2059 ConfigInputTextCursorBlink = true;
2060 ConfigInputTextEnterKeepActive = false;
2061 ConfigDragClickToInputText = false;
2062 ConfigWindowsResizeFromEdges = true;
2063 ConfigWindowsMoveFromTitleBarOnly = false;
2064 ConfigWindowsCopyContentsWithCtrlC = false;
2065 ConfigScrollbarScrollByPage = true;
2066 ConfigMemoryCompactTimer = 60.0f;
2067 ConfigDebugIsDebuggerPresent = false;
2068 ConfigDebugHighlightIdConflicts = true;
2069 ConfigDebugHighlightIdConflictsShowItemPicker = true;
2070 ConfigDebugBeginReturnValueOnce = false;
2071 ConfigDebugBeginReturnValueLoop = false;
2072
2073 ConfigErrorRecovery = true;
2074 ConfigErrorRecoveryEnableAssert = true;
2075 ConfigErrorRecoveryEnableDebugLog = true;
2076 ConfigErrorRecoveryEnableTooltip = true;
2077
2078 // Inputs Behaviors
2079 MouseDoubleClickTime = 0.30f;
2080 MouseDoubleClickMaxDist = 6.0f;
2081 MouseDragThreshold = 6.0f;
2082 KeyRepeatDelay = 0.275f;
2083 KeyRepeatRate = 0.050f;
2084
2085 // Platform Functions
2086 // Note: Initialize() will setup default clipboard/ime handlers.
2087 BackendPlatformName = BackendRendererName = NULL;
2088 BackendPlatformUserData = BackendRendererUserData = BackendLanguageUserData = NULL;
2089
2090 // Input (NB: we already have memset zero the entire structure!)
2091 MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
2092 MousePosPrev = ImVec2(-FLT_MAX, -FLT_MAX);
2093 MouseSource = ImGuiMouseSource_Mouse;
2094 for (int i = 0; i < IM_ARRAYSIZE(MouseDownDuration); i++)
2095 MouseDownDuration[i] = MouseDownDurationPrev[i] = -1.0f;
2096 for (int i = 0; i < IM_ARRAYSIZE(KeysData); i++)
2097 {
2098 KeysData[i].DownDuration = KeysData[i].DownDurationPrev = -1.0f;
2099 }
2100 AppAcceptingEvents = true;
2101}
2102
2103// Pass in translated ASCII characters for text input.
2104// - with glfw you can get those from the callback set in glfwSetCharCallback()
2105// - on Windows you can get those using ToAscii+keyboard state, or via the WM_CHAR message
2106// FIXME: Should in theory be called "AddCharacterEvent()" to be consistent with new API
2107void ImGuiIO::AddInputCharacter(unsigned int c)
2108{
2109 IM_ASSERT(Ctx != NULL);
2110 ImGuiContext &g = *Ctx;
2111 if (c == 0 || !AppAcceptingEvents)
2112 return;
2113
2115 e.Type = ImGuiInputEventType_Text;
2116 e.Source = ImGuiInputSource_Keyboard;
2117 e.EventId = g.InputEventsNextEventId++;
2118 e.Text.Char = c;
2119 g.InputEventsQueue.push_back(e);
2120}
2121
2122// UTF16 strings use surrogate pairs to encode codepoints >= 0x10000, so
2123// we should save the high surrogate.
2124void ImGuiIO::AddInputCharacterUTF16(ImWchar16 c)
2125{
2126 if ((c == 0 && InputQueueSurrogate == 0) || !AppAcceptingEvents)
2127 return;
2128
2129 if ((c & 0xFC00) == 0xD800) // High surrogate, must save
2130 {
2131 if (InputQueueSurrogate != 0)
2132 AddInputCharacter(IM_UNICODE_CODEPOINT_INVALID);
2133 InputQueueSurrogate = c;
2134 return;
2135 }
2136
2137 ImWchar cp = c;
2138 if (InputQueueSurrogate != 0)
2139 {
2140 if ((c & 0xFC00) != 0xDC00) // Invalid low surrogate
2141 {
2142 AddInputCharacter(IM_UNICODE_CODEPOINT_INVALID);
2143 }
2144 else
2145 {
2146#if IM_UNICODE_CODEPOINT_MAX == 0xFFFF
2147 cp = IM_UNICODE_CODEPOINT_INVALID; // Codepoint will not fit in ImWchar
2148#else
2149 cp = (ImWchar)(((InputQueueSurrogate - 0xD800) << 10) + (c - 0xDC00) + 0x10000);
2150#endif
2151 }
2152
2153 InputQueueSurrogate = 0;
2154 }
2155 AddInputCharacter((unsigned)cp);
2156}
2157
2158void ImGuiIO::AddInputCharactersUTF8(const char *utf8_chars)
2159{
2160 if (!AppAcceptingEvents)
2161 return;
2162 while (*utf8_chars != 0)
2163 {
2164 unsigned int c = 0;
2165 utf8_chars += ImTextCharFromUtf8(&c, utf8_chars, NULL);
2166 AddInputCharacter(c);
2167 }
2168}
2169
2170// Clear all incoming events.
2171void ImGuiIO::ClearEventsQueue()
2172{
2173 IM_ASSERT(Ctx != NULL);
2174 ImGuiContext &g = *Ctx;
2175 g.InputEventsQueue.clear();
2176}
2177
2178// Clear current keyboard/gamepad state + current frame text input buffer. Equivalent to releasing all keys/buttons.
2179void ImGuiIO::ClearInputKeys()
2180{
2181 ImGuiContext &g = *Ctx;
2182 for (int key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key++)
2183 {
2184 if (ImGui::IsMouseKey((ImGuiKey)key))
2185 continue;
2186 ImGuiKeyData *key_data = &g.IO.KeysData[key - ImGuiKey_NamedKey_BEGIN];
2187 key_data->Down = false;
2188 key_data->DownDuration = -1.0f;
2189 key_data->DownDurationPrev = -1.0f;
2190 }
2191 KeyCtrl = KeyShift = KeyAlt = KeySuper = false;
2192 KeyMods = ImGuiMod_None;
2193 InputQueueCharacters.resize(0); // Behavior of old ClearInputCharacters().
2194}
2195
2196void ImGuiIO::ClearInputMouse()
2197{
2198 for (ImGuiKey key = ImGuiKey_Mouse_BEGIN; key < ImGuiKey_Mouse_END; key = (ImGuiKey)(key + 1))
2199 {
2200 ImGuiKeyData *key_data = &KeysData[key - ImGuiKey_NamedKey_BEGIN];
2201 key_data->Down = false;
2202 key_data->DownDuration = -1.0f;
2203 key_data->DownDurationPrev = -1.0f;
2204 }
2205 MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
2206 for (int n = 0; n < IM_ARRAYSIZE(MouseDown); n++)
2207 {
2208 MouseDown[n] = false;
2209 MouseDownDuration[n] = MouseDownDurationPrev[n] = -1.0f;
2210 }
2211 MouseWheel = MouseWheelH = 0.0f;
2212}
2213
2214// Removed this as it is ambiguous/misleading and generally incorrect to use with the existence of a higher-level input
2215// queue. Current frame character buffer is now also cleared by ClearInputKeys().
2216#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
2217void ImGuiIO::ClearInputCharacters()
2218{
2219 InputQueueCharacters.resize(0);
2220}
2221#endif
2222
2223static ImGuiInputEvent *FindLatestInputEvent(ImGuiContext *ctx, ImGuiInputEventType type, int arg = -1)
2224{
2225 ImGuiContext &g = *ctx;
2226 for (int n = g.InputEventsQueue.Size - 1; n >= 0; n--)
2227 {
2228 ImGuiInputEvent *e = &g.InputEventsQueue[n];
2229 if (e->Type != type)
2230 continue;
2231 if (type == ImGuiInputEventType_Key && e->Key.Key != arg)
2232 continue;
2233 if (type == ImGuiInputEventType_MouseButton && e->MouseButton.Button != arg)
2234 continue;
2235 return e;
2236 }
2237 return NULL;
2238}
2239
2240// Queue a new key down/up event.
2241// - ImGuiKey key: Translated key (as in, generally ImGuiKey_A matches the key end-user would use to emit an 'A'
2242// character)
2243// - bool down: Is the key down? use false to signify a key release.
2244// - float analog_value: 0.0f..1.0f
2245// IMPORTANT: THIS FUNCTION AND OTHER "ADD" GRABS THE CONTEXT FROM OUR INSTANCE.
2246// WE NEED TO ENSURE THAT ALL FUNCTION CALLS ARE FULFILLING THIS, WHICH IS WHY GetKeyData() HAS AN EXPLICIT CONTEXT.
2247void ImGuiIO::AddKeyAnalogEvent(ImGuiKey key, bool down, float analog_value)
2248{
2249 // if (e->Down) { IMGUI_DEBUG_LOG_IO("AddKeyEvent() Key='%s' %d, NativeKeycode = %d, NativeScancode = %d\n",
2250 // ImGui::GetKeyName(e->Key), e->Down, e->NativeKeycode, e->NativeScancode); }
2251 IM_ASSERT(Ctx != NULL);
2252 if (key == ImGuiKey_None || !AppAcceptingEvents)
2253 return;
2254 ImGuiContext &g = *Ctx;
2255 IM_ASSERT(ImGui::IsNamedKeyOrMod(key)); // Backend needs to pass a valid ImGuiKey_ constant. 0..511 values are
2256 // legacy native key codes which are not accepted by this API.
2257 IM_ASSERT(ImGui::IsAliasKey(key) == false); // Backend cannot submit ImGuiKey_MouseXXX values they are automatically
2258 // inferred from AddMouseXXX() events.
2259
2260 // MacOS: swap Cmd(Super) and Ctrl
2261 if (g.IO.ConfigMacOSXBehaviors)
2262 {
2263 if (key == ImGuiMod_Super)
2264 {
2265 key = ImGuiMod_Ctrl;
2266 }
2267 else if (key == ImGuiMod_Ctrl)
2268 {
2269 key = ImGuiMod_Super;
2270 }
2271 else if (key == ImGuiKey_LeftSuper)
2272 {
2273 key = ImGuiKey_LeftCtrl;
2274 }
2275 else if (key == ImGuiKey_RightSuper)
2276 {
2277 key = ImGuiKey_RightCtrl;
2278 }
2279 else if (key == ImGuiKey_LeftCtrl)
2280 {
2281 key = ImGuiKey_LeftSuper;
2282 }
2283 else if (key == ImGuiKey_RightCtrl)
2284 {
2285 key = ImGuiKey_RightSuper;
2286 }
2287 }
2288
2289 // Filter duplicate (in particular: key mods and gamepad analog values are commonly spammed)
2290 const ImGuiInputEvent *latest_event = FindLatestInputEvent(&g, ImGuiInputEventType_Key, (int)key);
2291 const ImGuiKeyData *key_data = ImGui::GetKeyData(&g, key);
2292 const bool latest_key_down = latest_event ? latest_event->Key.Down : key_data->Down;
2293 const float latest_key_analog = latest_event ? latest_event->Key.AnalogValue : key_data->AnalogValue;
2294 if (latest_key_down == down && latest_key_analog == analog_value)
2295 return;
2296
2297 // Add event
2299 e.Type = ImGuiInputEventType_Key;
2300 e.Source = ImGui::IsGamepadKey(key) ? ImGuiInputSource_Gamepad : ImGuiInputSource_Keyboard;
2301 e.EventId = g.InputEventsNextEventId++;
2302 e.Key.Key = key;
2303 e.Key.Down = down;
2304 e.Key.AnalogValue = analog_value;
2305 g.InputEventsQueue.push_back(e);
2306}
2307
2308void ImGuiIO::AddKeyEvent(ImGuiKey key, bool down)
2309{
2310 if (!AppAcceptingEvents)
2311 return;
2312 AddKeyAnalogEvent(key, down, down ? 1.0f : 0.0f);
2313}
2314
2315// [Optional] Call after AddKeyEvent().
2316// Specify native keycode, scancode + Specify index for legacy <1.87 IsKeyXXX() functions with native indices.
2317// If you are writing a backend in 2022 or don't use IsKeyXXX() with native values that are not ImGuiKey values, you can
2318// avoid calling this.
2319void ImGuiIO::SetKeyEventNativeData(ImGuiKey key, int native_keycode, int native_scancode, int native_legacy_index)
2320{
2321 if (key == ImGuiKey_None)
2322 return;
2323 IM_ASSERT(ImGui::IsNamedKey(key)); // >= 512
2324 IM_ASSERT(native_legacy_index == -1 || ImGui::IsLegacyKey((ImGuiKey)native_legacy_index)); // >= 0 && <= 511
2325 IM_UNUSED(key); // Yet unused
2326 IM_UNUSED(native_keycode); // Yet unused
2327 IM_UNUSED(native_scancode); // Yet unused
2328 IM_UNUSED(native_legacy_index); // Yet unused
2329}
2330
2331// Set master flag for accepting key/mouse/text events (default to true). Useful if you have native dialog boxes that
2332// are interrupting your application loop/refresh, and you want to disable events being queued while your app is frozen.
2333void ImGuiIO::SetAppAcceptingEvents(bool accepting_events)
2334{
2335 AppAcceptingEvents = accepting_events;
2336}
2337
2338// Queue a mouse move event
2339void ImGuiIO::AddMousePosEvent(float x, float y)
2340{
2341 IM_ASSERT(Ctx != NULL);
2342 ImGuiContext &g = *Ctx;
2343 if (!AppAcceptingEvents)
2344 return;
2345
2346 // Apply same flooring as UpdateMouseInputs()
2347 ImVec2 pos((x > -FLT_MAX) ? ImFloor(x) : x, (y > -FLT_MAX) ? ImFloor(y) : y);
2348
2349 // Filter duplicate
2350 const ImGuiInputEvent *latest_event = FindLatestInputEvent(&g, ImGuiInputEventType_MousePos);
2351 const ImVec2 latest_pos =
2352 latest_event ? ImVec2(latest_event->MousePos.PosX, latest_event->MousePos.PosY) : g.IO.MousePos;
2353 if (latest_pos.x == pos.x && latest_pos.y == pos.y)
2354 return;
2355
2357 e.Type = ImGuiInputEventType_MousePos;
2358 e.Source = ImGuiInputSource_Mouse;
2359 e.EventId = g.InputEventsNextEventId++;
2360 e.MousePos.PosX = pos.x;
2361 e.MousePos.PosY = pos.y;
2362 e.MousePos.MouseSource = g.InputEventsNextMouseSource;
2363 g.InputEventsQueue.push_back(e);
2364}
2365
2366void ImGuiIO::AddMouseButtonEvent(int mouse_button, bool down)
2367{
2368 IM_ASSERT(Ctx != NULL);
2369 ImGuiContext &g = *Ctx;
2370 IM_ASSERT(mouse_button >= 0 && mouse_button < ImGuiMouseButton_COUNT);
2371 if (!AppAcceptingEvents)
2372 return;
2373
2374 // On MacOS X: Convert Ctrl(Super)+Left click into Right-click: handle held button.
2375 if (ConfigMacOSXBehaviors && mouse_button == 0 && MouseCtrlLeftAsRightClick)
2376 {
2377 // Order of both statements matters: this event will still release mouse button 1
2378 mouse_button = 1;
2379 if (!down)
2380 MouseCtrlLeftAsRightClick = false;
2381 }
2382
2383 // Filter duplicate
2384 const ImGuiInputEvent *latest_event = FindLatestInputEvent(&g, ImGuiInputEventType_MouseButton, (int)mouse_button);
2385 const bool latest_button_down = latest_event ? latest_event->MouseButton.Down : g.IO.MouseDown[mouse_button];
2386 if (latest_button_down == down)
2387 return;
2388
2389 // On MacOS X: Convert Ctrl(Super)+Left click into Right-click.
2390 // - Note that this is actual physical Ctrl which is ImGuiMod_Super for us.
2391 // - At this point we want from !down to down, so this is handling the initial press.
2392 if (ConfigMacOSXBehaviors && mouse_button == 0 && down)
2393 {
2394 const ImGuiInputEvent *latest_super_event =
2395 FindLatestInputEvent(&g, ImGuiInputEventType_Key, (int)ImGuiMod_Super);
2396 if (latest_super_event ? latest_super_event->Key.Down : g.IO.KeySuper)
2397 {
2398 IMGUI_DEBUG_LOG_IO("[io] Super+Left Click aliased into Right Click\n");
2399 MouseCtrlLeftAsRightClick = true;
2400 AddMouseButtonEvent(
2401 1, true); // This is just quicker to write that passing through, as we need to filter duplicate again.
2402 return;
2403 }
2404 }
2405
2407 e.Type = ImGuiInputEventType_MouseButton;
2408 e.Source = ImGuiInputSource_Mouse;
2409 e.EventId = g.InputEventsNextEventId++;
2410 e.MouseButton.Button = mouse_button;
2411 e.MouseButton.Down = down;
2412 e.MouseButton.MouseSource = g.InputEventsNextMouseSource;
2413 g.InputEventsQueue.push_back(e);
2414}
2415
2416// Queue a mouse wheel event (some mouse/API may only have a Y component)
2417void ImGuiIO::AddMouseWheelEvent(float wheel_x, float wheel_y)
2418{
2419 IM_ASSERT(Ctx != NULL);
2420 ImGuiContext &g = *Ctx;
2421
2422 // Filter duplicate (unlike most events, wheel values are relative and easy to filter)
2423 if (!AppAcceptingEvents || (wheel_x == 0.0f && wheel_y == 0.0f))
2424 return;
2425
2427 e.Type = ImGuiInputEventType_MouseWheel;
2428 e.Source = ImGuiInputSource_Mouse;
2429 e.EventId = g.InputEventsNextEventId++;
2430 e.MouseWheel.WheelX = wheel_x;
2431 e.MouseWheel.WheelY = wheel_y;
2432 e.MouseWheel.MouseSource = g.InputEventsNextMouseSource;
2433 g.InputEventsQueue.push_back(e);
2434}
2435
2436// This is not a real event, the data is latched in order to be stored in actual Mouse events.
2437// This is so that duplicate events (e.g. Windows sending extraneous WM_MOUSEMOVE) gets filtered and are not leading to
2438// actual source changes.
2439void ImGuiIO::AddMouseSourceEvent(ImGuiMouseSource source)
2440{
2441 IM_ASSERT(Ctx != NULL);
2442 ImGuiContext &g = *Ctx;
2443 g.InputEventsNextMouseSource = source;
2444}
2445
2446void ImGuiIO::AddMouseViewportEvent(ImGuiID viewport_id)
2447{
2448 IM_ASSERT(Ctx != NULL);
2449 ImGuiContext &g = *Ctx;
2450 // IM_ASSERT(g.IO.BackendFlags & ImGuiBackendFlags_HasMouseHoveredViewport);
2451 if (!AppAcceptingEvents)
2452 return;
2453
2454 // Filter duplicate
2455 const ImGuiInputEvent *latest_event = FindLatestInputEvent(&g, ImGuiInputEventType_MouseViewport);
2456 const ImGuiID latest_viewport_id =
2457 latest_event ? latest_event->MouseViewport.HoveredViewportID : g.IO.MouseHoveredViewport;
2458 if (latest_viewport_id == viewport_id)
2459 return;
2460
2462 e.Type = ImGuiInputEventType_MouseViewport;
2463 e.Source = ImGuiInputSource_Mouse;
2464 e.MouseViewport.HoveredViewportID = viewport_id;
2465 g.InputEventsQueue.push_back(e);
2466}
2467
2468void ImGuiIO::AddFocusEvent(bool focused)
2469{
2470 IM_ASSERT(Ctx != NULL);
2471 ImGuiContext &g = *Ctx;
2472
2473 // Filter duplicate
2474 const ImGuiInputEvent *latest_event = FindLatestInputEvent(&g, ImGuiInputEventType_Focus);
2475 const bool latest_focused = latest_event ? latest_event->AppFocused.Focused : !g.IO.AppFocusLost;
2476 if (latest_focused == focused || (ConfigDebugIgnoreFocusLoss && !focused))
2477 return;
2478
2480 e.Type = ImGuiInputEventType_Focus;
2481 e.EventId = g.InputEventsNextEventId++;
2482 e.AppFocused.Focused = focused;
2483 g.InputEventsQueue.push_back(e);
2484}
2485
2486ImGuiPlatformIO::ImGuiPlatformIO()
2487{
2488 // Most fields are initialized with zero
2489 memset(this, 0, sizeof(*this));
2490 Platform_LocaleDecimalPoint = '.';
2491}
2492
2493//-----------------------------------------------------------------------------
2494// [SECTION] MISC HELPERS/UTILITIES (Geometry functions)
2495//-----------------------------------------------------------------------------
2496
2497ImVec2 ImBezierCubicClosestPoint(const ImVec2 &p1, const ImVec2 &p2, const ImVec2 &p3, const ImVec2 &p4,
2498 const ImVec2 &p, int num_segments)
2499{
2500 IM_ASSERT(num_segments > 0); // Use ImBezierCubicClosestPointCasteljau()
2501 ImVec2 p_last = p1;
2502 ImVec2 p_closest;
2503 float p_closest_dist2 = FLT_MAX;
2504 float t_step = 1.0f / (float)num_segments;
2505 for (int i_step = 1; i_step <= num_segments; i_step++)
2506 {
2507 ImVec2 p_current = ImBezierCubicCalc(p1, p2, p3, p4, t_step * i_step);
2508 ImVec2 p_line = ImLineClosestPoint(p_last, p_current, p);
2509 float dist2 = ImLengthSqr(p - p_line);
2510 if (dist2 < p_closest_dist2)
2511 {
2512 p_closest = p_line;
2513 p_closest_dist2 = dist2;
2514 }
2515 p_last = p_current;
2516 }
2517 return p_closest;
2518}
2519
2520// Closely mimics PathBezierToCasteljau() in imgui_draw.cpp
2521static void ImBezierCubicClosestPointCasteljauStep(const ImVec2 &p, ImVec2 &p_closest, ImVec2 &p_last,
2522 float &p_closest_dist2, float x1, float y1, float x2, float y2,
2523 float x3, float y3, float x4, float y4, float tess_tol, int level)
2524{
2525 float dx = x4 - x1;
2526 float dy = y4 - y1;
2527 float d2 = ((x2 - x4) * dy - (y2 - y4) * dx);
2528 float d3 = ((x3 - x4) * dy - (y3 - y4) * dx);
2529 d2 = (d2 >= 0) ? d2 : -d2;
2530 d3 = (d3 >= 0) ? d3 : -d3;
2531 if ((d2 + d3) * (d2 + d3) < tess_tol * (dx * dx + dy * dy))
2532 {
2533 ImVec2 p_current(x4, y4);
2534 ImVec2 p_line = ImLineClosestPoint(p_last, p_current, p);
2535 float dist2 = ImLengthSqr(p - p_line);
2536 if (dist2 < p_closest_dist2)
2537 {
2538 p_closest = p_line;
2539 p_closest_dist2 = dist2;
2540 }
2541 p_last = p_current;
2542 }
2543 else if (level < 10)
2544 {
2545 float x12 = (x1 + x2) * 0.5f, y12 = (y1 + y2) * 0.5f;
2546 float x23 = (x2 + x3) * 0.5f, y23 = (y2 + y3) * 0.5f;
2547 float x34 = (x3 + x4) * 0.5f, y34 = (y3 + y4) * 0.5f;
2548 float x123 = (x12 + x23) * 0.5f, y123 = (y12 + y23) * 0.5f;
2549 float x234 = (x23 + x34) * 0.5f, y234 = (y23 + y34) * 0.5f;
2550 float x1234 = (x123 + x234) * 0.5f, y1234 = (y123 + y234) * 0.5f;
2551 ImBezierCubicClosestPointCasteljauStep(p, p_closest, p_last, p_closest_dist2, x1, y1, x12, y12, x123, y123,
2552 x1234, y1234, tess_tol, level + 1);
2553 ImBezierCubicClosestPointCasteljauStep(p, p_closest, p_last, p_closest_dist2, x1234, y1234, x234, y234, x34,
2554 y34, x4, y4, tess_tol, level + 1);
2555 }
2556}
2557
2558// tess_tol is generally the same value you would find in ImGui::GetStyle().CurveTessellationTol
2559// Because those ImXXX functions are lower-level than ImGui:: we cannot access this value automatically.
2560ImVec2 ImBezierCubicClosestPointCasteljau(const ImVec2 &p1, const ImVec2 &p2, const ImVec2 &p3, const ImVec2 &p4,
2561 const ImVec2 &p, float tess_tol)
2562{
2563 IM_ASSERT(tess_tol > 0.0f);
2564 ImVec2 p_last = p1;
2565 ImVec2 p_closest;
2566 float p_closest_dist2 = FLT_MAX;
2567 ImBezierCubicClosestPointCasteljauStep(p, p_closest, p_last, p_closest_dist2, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y,
2568 p4.x, p4.y, tess_tol, 0);
2569 return p_closest;
2570}
2571
2572ImVec2 ImLineClosestPoint(const ImVec2 &a, const ImVec2 &b, const ImVec2 &p)
2573{
2574 ImVec2 ap = p - a;
2575 ImVec2 ab_dir = b - a;
2576 float dot = ap.x * ab_dir.x + ap.y * ab_dir.y;
2577 if (dot < 0.0f)
2578 return a;
2579 float ab_len_sqr = ab_dir.x * ab_dir.x + ab_dir.y * ab_dir.y;
2580 if (dot > ab_len_sqr)
2581 return b;
2582 return a + ab_dir * dot / ab_len_sqr;
2583}
2584
2585bool ImTriangleContainsPoint(const ImVec2 &a, const ImVec2 &b, const ImVec2 &c, const ImVec2 &p)
2586{
2587 bool b1 = ((p.x - b.x) * (a.y - b.y) - (p.y - b.y) * (a.x - b.x)) < 0.0f;
2588 bool b2 = ((p.x - c.x) * (b.y - c.y) - (p.y - c.y) * (b.x - c.x)) < 0.0f;
2589 bool b3 = ((p.x - a.x) * (c.y - a.y) - (p.y - a.y) * (c.x - a.x)) < 0.0f;
2590 return ((b1 == b2) && (b2 == b3));
2591}
2592
2593void ImTriangleBarycentricCoords(const ImVec2 &a, const ImVec2 &b, const ImVec2 &c, const ImVec2 &p, float &out_u,
2594 float &out_v, float &out_w)
2595{
2596 ImVec2 v0 = b - a;
2597 ImVec2 v1 = c - a;
2598 ImVec2 v2 = p - a;
2599 const float denom = v0.x * v1.y - v1.x * v0.y;
2600 out_v = (v2.x * v1.y - v1.x * v2.y) / denom;
2601 out_w = (v0.x * v2.y - v2.x * v0.y) / denom;
2602 out_u = 1.0f - out_v - out_w;
2603}
2604
2605ImVec2 ImTriangleClosestPoint(const ImVec2 &a, const ImVec2 &b, const ImVec2 &c, const ImVec2 &p)
2606{
2607 ImVec2 proj_ab = ImLineClosestPoint(a, b, p);
2608 ImVec2 proj_bc = ImLineClosestPoint(b, c, p);
2609 ImVec2 proj_ca = ImLineClosestPoint(c, a, p);
2610 float dist2_ab = ImLengthSqr(p - proj_ab);
2611 float dist2_bc = ImLengthSqr(p - proj_bc);
2612 float dist2_ca = ImLengthSqr(p - proj_ca);
2613 float m = ImMin(dist2_ab, ImMin(dist2_bc, dist2_ca));
2614 if (m == dist2_ab)
2615 return proj_ab;
2616 if (m == dist2_bc)
2617 return proj_bc;
2618 return proj_ca;
2619}
2620
2621//-----------------------------------------------------------------------------
2622// [SECTION] MISC HELPERS/UTILITIES (String, Format, Hash functions)
2623//-----------------------------------------------------------------------------
2624
2625// Consider using _stricmp/_strnicmp under Windows or strcasecmp/strncasecmp. We don't actually use either
2626// ImStricmp/ImStrnicmp in the codebase any more.
2627int ImStricmp(const char *str1, const char *str2)
2628{
2629 int d;
2630 while ((d = ImToUpper(*str2) - ImToUpper(*str1)) == 0 && *str1)
2631 {
2632 str1++;
2633 str2++;
2634 }
2635 return d;
2636}
2637
2638int ImStrnicmp(const char *str1, const char *str2, size_t count)
2639{
2640 int d = 0;
2641 while (count > 0 && (d = ImToUpper(*str2) - ImToUpper(*str1)) == 0 && *str1)
2642 {
2643 str1++;
2644 str2++;
2645 count--;
2646 }
2647 return d;
2648}
2649
2650void ImStrncpy(char *dst, const char *src, size_t count)
2651{
2652 if (count < 1)
2653 return;
2654 if (count > 1)
2655 strncpy(dst, src, count - 1);
2656 dst[count - 1] = 0;
2657}
2658
2659char *ImStrdup(const char *str)
2660{
2661 size_t len = ImStrlen(str);
2662 void *buf = IM_ALLOC(len + 1);
2663 return (char *)memcpy(buf, (const void *)str, len + 1);
2664}
2665
2666char *ImStrdupcpy(char *dst, size_t *p_dst_size, const char *src)
2667{
2668 size_t dst_buf_size = p_dst_size ? *p_dst_size : ImStrlen(dst) + 1;
2669 size_t src_size = ImStrlen(src) + 1;
2670 if (dst_buf_size < src_size)
2671 {
2672 IM_FREE(dst);
2673 dst = (char *)IM_ALLOC(src_size);
2674 if (p_dst_size)
2675 *p_dst_size = src_size;
2676 }
2677 return (char *)memcpy(dst, (const void *)src, src_size);
2678}
2679
2680const char *ImStrchrRange(const char *str, const char *str_end, char c)
2681{
2682 const char *p = (const char *)ImMemchr(str, (int)c, str_end - str);
2683 return p;
2684}
2685
2686int ImStrlenW(const ImWchar *str)
2687{
2688 // return (int)wcslen((const wchar_t*)str); // FIXME-OPT: Could use this when wchar_t are 16-bit
2689 int n = 0;
2690 while (*str++)
2691 n++;
2692 return n;
2693}
2694
2695// Find end-of-line. Return pointer will point to either first \n, either str_end.
2696const char *ImStreolRange(const char *str, const char *str_end)
2697{
2698 const char *p = (const char *)ImMemchr(str, '\n', str_end - str);
2699 return p ? p : str_end;
2700}
2701
2702const char *ImStrbol(const char *buf_mid_line, const char *buf_begin) // find beginning-of-line
2703{
2704 IM_ASSERT_PARANOID(buf_mid_line >= buf_begin && buf_mid_line <= buf_begin + ImStrlen(buf_begin));
2705 while (buf_mid_line > buf_begin && buf_mid_line[-1] != '\n')
2706 buf_mid_line--;
2707 return buf_mid_line;
2708}
2709
2710const char *ImStristr(const char *haystack, const char *haystack_end, const char *needle, const char *needle_end)
2711{
2712 if (!needle_end)
2713 needle_end = needle + ImStrlen(needle);
2714
2715 const char un0 = (char)ImToUpper(*needle);
2716 while ((!haystack_end && *haystack) || (haystack_end && haystack < haystack_end))
2717 {
2718 if (ImToUpper(*haystack) == un0)
2719 {
2720 const char *b = needle + 1;
2721 for (const char *a = haystack + 1; b < needle_end; a++, b++)
2722 if (ImToUpper(*a) != ImToUpper(*b))
2723 break;
2724 if (b == needle_end)
2725 return haystack;
2726 }
2727 haystack++;
2728 }
2729 return NULL;
2730}
2731
2732// Trim str by offsetting contents when there's leading data + writing a \0 at the trailing position. We use this in
2733// situation where the cost is negligible.
2734void ImStrTrimBlanks(char *buf)
2735{
2736 char *p = buf;
2737 while (p[0] == ' ' || p[0] == '\t') // Leading blanks
2738 p++;
2739 char *p_start = p;
2740 while (*p != 0) // Find end of string
2741 p++;
2742 while (p > p_start && (p[-1] == ' ' || p[-1] == '\t')) // Trailing blanks
2743 p--;
2744 if (p_start != buf) // Copy memory if we had leading blanks
2745 memmove(buf, p_start, p - p_start);
2746 buf[p - p_start] = 0; // Zero terminate
2747}
2748
2749const char *ImStrSkipBlank(const char *str)
2750{
2751 while (str[0] == ' ' || str[0] == '\t')
2752 str++;
2753 return str;
2754}
2755
2756// A) MSVC version appears to return -1 on overflow, whereas glibc appears to return total count (which may be >=
2757// buf_size). Ideally we would test for only one of those limits at runtime depending on the behavior the vsnprintf(),
2758// but trying to deduct it at compile time sounds like a pandora can of worm. B) When buf==NULL vsnprintf() will return
2759// the output size.
2760#ifndef IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS
2761
2762// We support stb_sprintf which is much faster (see: https://github.com/nothings/stb/blob/master/stb_sprintf.h)
2763// You may set IMGUI_USE_STB_SPRINTF to use our default wrapper, or set IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS
2764// and setup the wrapper yourself. (FIXME-OPT: Some of our high-level operations such as ImGuiTextBuffer::appendfv() are
2765// designed using two-passes worst case, which probably could be improved using the stbsp_vsprintfcb() function.)
2766#ifdef IMGUI_USE_STB_SPRINTF
2767#ifndef IMGUI_DISABLE_STB_SPRINTF_IMPLEMENTATION
2768#define STB_SPRINTF_IMPLEMENTATION
2769#endif
2770#ifdef IMGUI_STB_SPRINTF_FILENAME
2771#include IMGUI_STB_SPRINTF_FILENAME
2772#else
2773#include "stb_sprintf.h"
2774#endif
2775#endif // #ifdef IMGUI_USE_STB_SPRINTF
2776
2777#if defined(_MSC_VER) && !defined(vsnprintf)
2778#define vsnprintf _vsnprintf
2779#endif
2780
2781int ImFormatString(char *buf, size_t buf_size, const char *fmt, ...)
2782{
2783 va_list args;
2784 va_start(args, fmt);
2785#ifdef IMGUI_USE_STB_SPRINTF
2786 int w = stbsp_vsnprintf(buf, (int)buf_size, fmt, args);
2787#else
2788 int w = vsnprintf(buf, buf_size, fmt, args);
2789#endif
2790 va_end(args);
2791 if (buf == NULL)
2792 return w;
2793 if (w == -1 || w >= (int)buf_size)
2794 w = (int)buf_size - 1;
2795 buf[w] = 0;
2796 return w;
2797}
2798
2799int ImFormatStringV(char *buf, size_t buf_size, const char *fmt, va_list args)
2800{
2801#ifdef IMGUI_USE_STB_SPRINTF
2802 int w = stbsp_vsnprintf(buf, (int)buf_size, fmt, args);
2803#else
2804 int w = vsnprintf(buf, buf_size, fmt, args);
2805#endif
2806 if (buf == NULL)
2807 return w;
2808 if (w == -1 || w >= (int)buf_size)
2809 w = (int)buf_size - 1;
2810 buf[w] = 0;
2811 return w;
2812}
2813#endif // #ifdef IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS
2814
2815void ImFormatStringToTempBuffer(const char **out_buf, const char **out_buf_end, const char *fmt, ...)
2816{
2817 va_list args;
2818 va_start(args, fmt);
2819 ImFormatStringToTempBufferV(out_buf, out_buf_end, fmt, args);
2820 va_end(args);
2821}
2822
2823// FIXME: Should rework API toward allowing multiple in-flight temp buffers (easier and safer for caller)
2824// by making the caller acquire a temp buffer token, with either explicit or destructor release, e.g.
2825// ImGuiTempBufferToken token;
2826// ImFormatStringToTempBuffer(token, ...);
2827void ImFormatStringToTempBufferV(const char **out_buf, const char **out_buf_end, const char *fmt, va_list args)
2828{
2829 ImGuiContext &g = *GImGui;
2830 if (fmt[0] == '%' && fmt[1] == 's' && fmt[2] == 0)
2831 {
2832 const char *buf = va_arg(args, const char *); // Skip formatting when using "%s"
2833 if (buf == NULL)
2834 buf = "(null)";
2835 *out_buf = buf;
2836 if (out_buf_end)
2837 {
2838 *out_buf_end = buf + ImStrlen(buf);
2839 }
2840 }
2841 else if (fmt[0] == '%' && fmt[1] == '.' && fmt[2] == '*' && fmt[3] == 's' && fmt[4] == 0)
2842 {
2843 int buf_len = va_arg(args, int); // Skip formatting when using "%.*s"
2844 const char *buf = va_arg(args, const char *);
2845 if (buf == NULL)
2846 {
2847 buf = "(null)";
2848 buf_len = ImMin(buf_len, 6);
2849 }
2850 *out_buf = buf;
2851 *out_buf_end = buf + buf_len; // Disallow not passing 'out_buf_end' here. User is expected to use it.
2852 }
2853 else
2854 {
2855 int buf_len = ImFormatStringV(g.TempBuffer.Data, g.TempBuffer.Size, fmt, args);
2856 *out_buf = g.TempBuffer.Data;
2857 if (out_buf_end)
2858 {
2859 *out_buf_end = g.TempBuffer.Data + buf_len;
2860 }
2861 }
2862}
2863
2864#ifndef IMGUI_ENABLE_SSE4_2_CRC
2865// CRC32 needs a 1KB lookup table (not cache friendly)
2866// Although the code to generate the table is simple and shorter than the table itself, using a const table allows us to
2867// easily:
2868// - avoid an unnecessary branch/memory tap, - keep the ImHashXXX functions usable by static constructors, - make it
2869// thread-safe.
2870static const ImU32 GCrc32LookupTable[256] = {
2871#ifdef IMGUI_USE_LEGACY_CRC32_ADLER
2872 // Legacy CRC32-adler table used pre 1.91.6 (before 2024/11/27). Only use if you cannot afford invalidating old .ini
2873 // data.
2874 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832,
2875 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2,
2876 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A,
2877 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
2878 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3,
2879 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423,
2880 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB,
2881 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
2882 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4,
2883 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950,
2884 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074,
2885 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
2886 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525,
2887 0x206F85B3, 0xB966D409, 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81,
2888 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615,
2889 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
2890 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76,
2891 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E,
2892 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6,
2893 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
2894 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7,
2895 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F,
2896 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7,
2897 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
2898 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278,
2899 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC,
2900 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330,
2901 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
2902 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D,
2903#else
2904 // CRC32c table compatible with SSE 4.2 instructions
2905 0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4, 0xC79A971F, 0x35F1141C, 0x26A1E7E8, 0xD4CA64EB, 0x8AD958CF,
2906 0x78B2DBCC, 0x6BE22838, 0x9989AB3B, 0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24, 0x105EC76F, 0xE235446C,
2907 0xF165B798, 0x030E349B, 0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384, 0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57,
2908 0x89D76C54, 0x5D1D08BF, 0xAF768BBC, 0xBC267848, 0x4E4DFB4B, 0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A,
2909 0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35, 0xAA64D611, 0x580F5512, 0x4B5FA6E6, 0xB93425E5, 0x6DFE410E,
2910 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA, 0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45, 0xF779DEAE, 0x05125DAD,
2911 0x1642AE59, 0xE4292D5A, 0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A, 0x7DA08661, 0x8FCB0562, 0x9C9BF696,
2912 0x6EF07595, 0x417B1DBC, 0xB3109EBF, 0xA0406D4B, 0x522BEE48, 0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957,
2913 0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687, 0x0C38D26C, 0xFE53516F, 0xED03A29B, 0x1F682198, 0x5125DAD3,
2914 0xA34E59D0, 0xB01EAA24, 0x42752927, 0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38, 0xDBFC821C, 0x2997011F,
2915 0x3AC7F2EB, 0xC8AC71E8, 0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7, 0x61C69362, 0x93AD1061, 0x80FDE395,
2916 0x72966096, 0xA65C047D, 0x5437877E, 0x4767748A, 0xB50CF789, 0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859,
2917 0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46, 0x7198540D, 0x83F3D70E, 0x90A324FA, 0x62C8A7F9, 0xB602C312,
2918 0x44694011, 0x5739B3E5, 0xA55230E6, 0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36, 0x3CDB9BDD, 0xCEB018DE,
2919 0xDDE0EB2A, 0x2F8B6829, 0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C, 0x456CAC67, 0xB7072F64, 0xA457DC90,
2920 0x563C5F93, 0x082F63B7, 0xFA44E0B4, 0xE9141340, 0x1B7F9043, 0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C,
2921 0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3, 0x55326B08, 0xA759E80B, 0xB4091BFF, 0x466298FC, 0x1871A4D8,
2922 0xEA1A27DB, 0xF94AD42F, 0x0B21572C, 0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033, 0xA24BB5A6, 0x502036A5,
2923 0x4370C551, 0xB11B4652, 0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D, 0x2892ED69, 0xDAF96E6A, 0xC9A99D9E,
2924 0x3BC21E9D, 0xEF087A76, 0x1D63F975, 0x0E330A81, 0xFC588982, 0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D,
2925 0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622, 0x38CC2A06, 0xCAA7A905, 0xD9F75AF1, 0x2B9CD9F2, 0xFF56BD19,
2926 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED, 0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530, 0x0417B1DB, 0xF67C32D8,
2927 0xE52CC12C, 0x1747422F, 0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF, 0x8ECEE914, 0x7CA56A17, 0x6FF599E3,
2928 0x9D9E1AE0, 0xD3D3E1AB, 0x21B862A8, 0x32E8915C, 0xC083125F, 0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540,
2929 0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90, 0x9E902E7B, 0x6CFBAD78, 0x7FAB5E8C, 0x8DC0DD8F, 0xE330A81A,
2930 0x115B2B19, 0x020BD8ED, 0xF0605BEE, 0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1, 0x69E9F0D5, 0x9B8273D6,
2931 0x88D28022, 0x7AB90321, 0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E, 0xF36E6F75, 0x0105EC76, 0x12551F82,
2932 0xE03E9C81, 0x34F4F86A, 0xC69F7B69, 0xD5CF889D, 0x27A40B9E, 0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E,
2933 0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351
2934#endif
2935};
2936#endif
2937
2938// Known size hash
2939// It is ok to call ImHashData on a string with known length but the ### operator won't be supported.
2940// FIXME-OPT: Replace with e.g. FNV1a hash? CRC32 pretty much randomly access 1KB. Need to do proper measurements.
2941ImGuiID ImHashData(const void *data_p, size_t data_size, ImGuiID seed)
2942{
2943 ImU32 crc = ~seed;
2944 const unsigned char *data = (const unsigned char *)data_p;
2945 const unsigned char *data_end = (const unsigned char *)data_p + data_size;
2946#ifndef IMGUI_ENABLE_SSE4_2_CRC
2947 const ImU32 *crc32_lut = GCrc32LookupTable;
2948 while (data < data_end)
2949 crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ *data++];
2950 return ~crc;
2951#else
2952 while (data + 4 <= data_end)
2953 {
2954 crc = _mm_crc32_u32(crc, *(ImU32 *)data);
2955 data += 4;
2956 }
2957 while (data < data_end)
2958 crc = _mm_crc32_u8(crc, *data++);
2959 return ~crc;
2960#endif
2961}
2962
2963// Zero-terminated string hash, with support for ### to reset back to seed value
2964// We support a syntax of "label###id" where only "###id" is included in the hash, and only "label" gets displayed.
2965// Because this syntax is rarely used we are optimizing for the common case.
2966// - If we reach ### in the string we discard the hash so far and reset to the seed.
2967// - We don't do 'current += 2; continue;' after handling ### to keep the code smaller/faster (measured ~10% diff in
2968// Debug build)
2969// FIXME-OPT: Replace with e.g. FNV1a hash? CRC32 pretty much randomly access 1KB. Need to do proper measurements.
2970ImGuiID ImHashStr(const char *data_p, size_t data_size, ImGuiID seed)
2971{
2972 seed = ~seed;
2973 ImU32 crc = seed;
2974 const unsigned char *data = (const unsigned char *)data_p;
2975#ifndef IMGUI_ENABLE_SSE4_2_CRC
2976 const ImU32 *crc32_lut = GCrc32LookupTable;
2977#endif
2978 if (data_size != 0)
2979 {
2980 while (data_size-- != 0)
2981 {
2982 unsigned char c = *data++;
2983 if (c == '#' && data_size >= 2 && data[0] == '#' && data[1] == '#')
2984 crc = seed;
2985#ifndef IMGUI_ENABLE_SSE4_2_CRC
2986 crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c];
2987#else
2988 crc = _mm_crc32_u8(crc, c);
2989#endif
2990 }
2991 }
2992 else
2993 {
2994 while (unsigned char c = *data++)
2995 {
2996 if (c == '#' && data[0] == '#' && data[1] == '#')
2997 crc = seed;
2998#ifndef IMGUI_ENABLE_SSE4_2_CRC
2999 crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c];
3000#else
3001 crc = _mm_crc32_u8(crc, c);
3002#endif
3003 }
3004 }
3005 return ~crc;
3006}
3007
3008//-----------------------------------------------------------------------------
3009// [SECTION] MISC HELPERS/UTILITIES (File functions)
3010//-----------------------------------------------------------------------------
3011
3012// Default file functions
3013#ifndef IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS
3014
3015ImFileHandle ImFileOpen(const char *filename, const char *mode)
3016{
3017#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && \
3018 (defined(__MINGW32__) || (!defined(__CYGWIN__) && !defined(__GNUC__)))
3019 // We need a fopen() wrapper because MSVC/Windows fopen doesn't handle UTF-8 filenames.
3020 // Previously we used ImTextCountCharsFromUtf8/ImTextStrFromUtf8 here but we now need to support ImWchar16 and
3021 // ImWchar32!
3022 const int filename_wsize = ::MultiByteToWideChar(CP_UTF8, 0, filename, -1, NULL, 0);
3023 const int mode_wsize = ::MultiByteToWideChar(CP_UTF8, 0, mode, -1, NULL, 0);
3024
3025 // Use stack buffer if possible, otherwise heap buffer. Sizes include zero terminator.
3026 // We don't rely on current ImGuiContext as this is implied to be a helper function which doesn't depend on it (see
3027 // #7314).
3028 wchar_t local_temp_stack[FILENAME_MAX];
3029 ImVector<wchar_t> local_temp_heap;
3030 if (filename_wsize + mode_wsize > IM_ARRAYSIZE(local_temp_stack))
3031 local_temp_heap.resize(filename_wsize + mode_wsize);
3032 wchar_t *filename_wbuf = local_temp_heap.Data ? local_temp_heap.Data : local_temp_stack;
3033 wchar_t *mode_wbuf = filename_wbuf + filename_wsize;
3034 ::MultiByteToWideChar(CP_UTF8, 0, filename, -1, filename_wbuf, filename_wsize);
3035 ::MultiByteToWideChar(CP_UTF8, 0, mode, -1, mode_wbuf, mode_wsize);
3036 return ::_wfopen(filename_wbuf, mode_wbuf);
3037#else
3038 return fopen(filename, mode);
3039#endif
3040}
3041
3042// We should in theory be using fseeko()/ftello() with off_t and _fseeki64()/_ftelli64() with __int64, waiting for the
3043// PR that does that in a very portable pre-C++11 zero-warnings way.
3044bool ImFileClose(ImFileHandle f)
3045{
3046 return fclose(f) == 0;
3047}
3048ImU64 ImFileGetSize(ImFileHandle f)
3049{
3050 long off = 0, sz = 0;
3051 return ((off = ftell(f)) != -1 && !fseek(f, 0, SEEK_END) && (sz = ftell(f)) != -1 && !fseek(f, off, SEEK_SET))
3052 ? (ImU64)sz
3053 : (ImU64)-1;
3054}
3055ImU64 ImFileRead(void *data, ImU64 sz, ImU64 count, ImFileHandle f)
3056{
3057 return fread(data, (size_t)sz, (size_t)count, f);
3058}
3059ImU64 ImFileWrite(const void *data, ImU64 sz, ImU64 count, ImFileHandle f)
3060{
3061 return fwrite(data, (size_t)sz, (size_t)count, f);
3062}
3063#endif // #ifndef IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS
3064
3065// Helper: Load file content into memory
3066// Memory allocated with IM_ALLOC(), must be freed by user using IM_FREE() == ImGui::MemFree()
3067// This can't really be used with "rt" because fseek size won't match read size.
3068void *ImFileLoadToMemory(const char *filename, const char *mode, size_t *out_file_size, int padding_bytes)
3069{
3070 IM_ASSERT(filename && mode);
3071 if (out_file_size)
3072 *out_file_size = 0;
3073
3074 ImFileHandle f;
3075 if ((f = ImFileOpen(filename, mode)) == NULL)
3076 return NULL;
3077
3078 size_t file_size = (size_t)ImFileGetSize(f);
3079 if (file_size == (size_t)-1)
3080 {
3081 ImFileClose(f);
3082 return NULL;
3083 }
3084
3085 void *file_data = IM_ALLOC(file_size + padding_bytes);
3086 if (file_data == NULL)
3087 {
3088 ImFileClose(f);
3089 return NULL;
3090 }
3091 if (ImFileRead(file_data, 1, file_size, f) != file_size)
3092 {
3093 ImFileClose(f);
3094 IM_FREE(file_data);
3095 return NULL;
3096 }
3097 if (padding_bytes > 0)
3098 memset((void *)(((char *)file_data) + file_size), 0, (size_t)padding_bytes);
3099
3100 ImFileClose(f);
3101 if (out_file_size)
3102 *out_file_size = file_size;
3103
3104 return file_data;
3105}
3106
3107//-----------------------------------------------------------------------------
3108// [SECTION] MISC HELPERS/UTILITIES (ImText* functions)
3109//-----------------------------------------------------------------------------
3110
3111IM_MSVC_RUNTIME_CHECKS_OFF
3112
3113// Convert UTF-8 to 32-bit character, process single character input.
3114// A nearly-branchless UTF-8 decoder, based on work of Christopher Wellons (https://github.com/skeeto/branchless-utf8).
3115// We handle UTF-8 decoding error by skipping forward.
3116int ImTextCharFromUtf8(unsigned int *out_char, const char *in_text, const char *in_text_end)
3117{
3118 static const char lengths[32] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
3119 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 3, 3, 4, 0};
3120 static const int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07};
3121 static const uint32_t mins[] = {0x400000, 0, 0x80, 0x800, 0x10000};
3122 static const int shiftc[] = {0, 18, 12, 6, 0};
3123 static const int shifte[] = {0, 6, 4, 2, 0};
3124 int len = lengths[*(const unsigned char *)in_text >> 3];
3125 int wanted = len + (len ? 0 : 1);
3126
3127 if (in_text_end == NULL)
3128 in_text_end = in_text + wanted; // Max length, nulls will be taken into account.
3129
3130 // Copy at most 'len' bytes, stop copying at 0 or past in_text_end. Branch predictor does a good job here,
3131 // so it is fast even with excessive branching.
3132 unsigned char s[4];
3133 s[0] = in_text + 0 < in_text_end ? in_text[0] : 0;
3134 s[1] = in_text + 1 < in_text_end ? in_text[1] : 0;
3135 s[2] = in_text + 2 < in_text_end ? in_text[2] : 0;
3136 s[3] = in_text + 3 < in_text_end ? in_text[3] : 0;
3137
3138 // Assume a four-byte character and load four bytes. Unused bits are shifted out.
3139 *out_char = (uint32_t)(s[0] & masks[len]) << 18;
3140 *out_char |= (uint32_t)(s[1] & 0x3f) << 12;
3141 *out_char |= (uint32_t)(s[2] & 0x3f) << 6;
3142 *out_char |= (uint32_t)(s[3] & 0x3f) << 0;
3143 *out_char >>= shiftc[len];
3144
3145 // Accumulate the various error conditions.
3146 int e = 0;
3147 e = (*out_char < mins[len]) << 6; // non-canonical encoding
3148 e |= ((*out_char >> 11) == 0x1b) << 7; // surrogate half?
3149 e |= (*out_char > IM_UNICODE_CODEPOINT_MAX) << 8; // out of range we can store in ImWchar (FIXME: May evolve)
3150 e |= (s[1] & 0xc0) >> 2;
3151 e |= (s[2] & 0xc0) >> 4;
3152 e |= (s[3]) >> 6;
3153 e ^= 0x2a; // top two bits of each tail byte correct?
3154 e >>= shifte[len];
3155
3156 if (e)
3157 {
3158 // No bytes are consumed when *in_text == 0 || in_text == in_text_end.
3159 // One byte is consumed in case of invalid first byte of in_text.
3160 // All available bytes (at most `len` bytes) are consumed on incomplete/invalid second to last bytes.
3161 // Invalid or incomplete input may consume less bytes than wanted, therefore every byte has to be inspected in
3162 // s.
3163 wanted = ImMin(wanted, !!s[0] + !!s[1] + !!s[2] + !!s[3]);
3164 *out_char = IM_UNICODE_CODEPOINT_INVALID;
3165 }
3166
3167 return wanted;
3168}
3169
3170int ImTextStrFromUtf8(ImWchar *buf, int buf_size, const char *in_text, const char *in_text_end,
3171 const char **in_text_remaining)
3172{
3173 ImWchar *buf_out = buf;
3174 ImWchar *buf_end = buf + buf_size;
3175 while (buf_out < buf_end - 1 && (!in_text_end || in_text < in_text_end) && *in_text)
3176 {
3177 unsigned int c;
3178 in_text += ImTextCharFromUtf8(&c, in_text, in_text_end);
3179 *buf_out++ = (ImWchar)c;
3180 }
3181 *buf_out = 0;
3182 if (in_text_remaining)
3183 *in_text_remaining = in_text;
3184 return (int)(buf_out - buf);
3185}
3186
3187int ImTextCountCharsFromUtf8(const char *in_text, const char *in_text_end)
3188{
3189 int char_count = 0;
3190 while ((!in_text_end || in_text < in_text_end) && *in_text)
3191 {
3192 unsigned int c;
3193 in_text += ImTextCharFromUtf8(&c, in_text, in_text_end);
3194 char_count++;
3195 }
3196 return char_count;
3197}
3198
3199// Based on stb_to_utf8() from github.com/nothings/stb/
3200static inline int ImTextCharToUtf8_inline(char *buf, int buf_size, unsigned int c)
3201{
3202 if (c < 0x80)
3203 {
3204 buf[0] = (char)c;
3205 return 1;
3206 }
3207 if (c < 0x800)
3208 {
3209 if (buf_size < 2)
3210 return 0;
3211 buf[0] = (char)(0xc0 + (c >> 6));
3212 buf[1] = (char)(0x80 + (c & 0x3f));
3213 return 2;
3214 }
3215 if (c < 0x10000)
3216 {
3217 if (buf_size < 3)
3218 return 0;
3219 buf[0] = (char)(0xe0 + (c >> 12));
3220 buf[1] = (char)(0x80 + ((c >> 6) & 0x3f));
3221 buf[2] = (char)(0x80 + ((c) & 0x3f));
3222 return 3;
3223 }
3224 if (c <= 0x10FFFF)
3225 {
3226 if (buf_size < 4)
3227 return 0;
3228 buf[0] = (char)(0xf0 + (c >> 18));
3229 buf[1] = (char)(0x80 + ((c >> 12) & 0x3f));
3230 buf[2] = (char)(0x80 + ((c >> 6) & 0x3f));
3231 buf[3] = (char)(0x80 + ((c) & 0x3f));
3232 return 4;
3233 }
3234 // Invalid code point, the max unicode is 0x10FFFF
3235 return 0;
3236}
3237
3238const char *ImTextCharToUtf8(char out_buf[5], unsigned int c)
3239{
3240 int count = ImTextCharToUtf8_inline(out_buf, 5, c);
3241 out_buf[count] = 0;
3242 return out_buf;
3243}
3244
3245// Not optimal but we very rarely use this function.
3246int ImTextCountUtf8BytesFromChar(const char *in_text, const char *in_text_end)
3247{
3248 unsigned int unused = 0;
3249 return ImTextCharFromUtf8(&unused, in_text, in_text_end);
3250}
3251
3252static inline int ImTextCountUtf8BytesFromChar(unsigned int c)
3253{
3254 if (c < 0x80)
3255 return 1;
3256 if (c < 0x800)
3257 return 2;
3258 if (c < 0x10000)
3259 return 3;
3260 if (c <= 0x10FFFF)
3261 return 4;
3262 return 3;
3263}
3264
3265int ImTextStrToUtf8(char *out_buf, int out_buf_size, const ImWchar *in_text, const ImWchar *in_text_end)
3266{
3267 char *buf_p = out_buf;
3268 const char *buf_end = out_buf + out_buf_size;
3269 while (buf_p < buf_end - 1 && (!in_text_end || in_text < in_text_end) && *in_text)
3270 {
3271 unsigned int c = (unsigned int)(*in_text++);
3272 if (c < 0x80)
3273 *buf_p++ = (char)c;
3274 else
3275 buf_p += ImTextCharToUtf8_inline(buf_p, (int)(buf_end - buf_p - 1), c);
3276 }
3277 *buf_p = 0;
3278 return (int)(buf_p - out_buf);
3279}
3280
3281int ImTextCountUtf8BytesFromStr(const ImWchar *in_text, const ImWchar *in_text_end)
3282{
3283 int bytes_count = 0;
3284 while ((!in_text_end || in_text < in_text_end) && *in_text)
3285 {
3286 unsigned int c = (unsigned int)(*in_text++);
3287 if (c < 0x80)
3288 bytes_count++;
3289 else
3290 bytes_count += ImTextCountUtf8BytesFromChar(c);
3291 }
3292 return bytes_count;
3293}
3294
3295const char *ImTextFindPreviousUtf8Codepoint(const char *in_text_start, const char *in_text_curr)
3296{
3297 while (in_text_curr > in_text_start)
3298 {
3299 in_text_curr--;
3300 if ((*in_text_curr & 0xC0) != 0x80)
3301 return in_text_curr;
3302 }
3303 return in_text_start;
3304}
3305
3306int ImTextCountLines(const char *in_text, const char *in_text_end)
3307{
3308 if (in_text_end == NULL)
3309 in_text_end = in_text + ImStrlen(in_text); // FIXME-OPT: Not optimal approach, discourage use for now.
3310 int count = 0;
3311 while (in_text < in_text_end)
3312 {
3313 const char *line_end = (const char *)ImMemchr(in_text, '\n', in_text_end - in_text);
3314 in_text = line_end ? line_end + 1 : in_text_end;
3315 count++;
3316 }
3317 return count;
3318}
3319
3320IM_MSVC_RUNTIME_CHECKS_RESTORE
3321
3322//-----------------------------------------------------------------------------
3323// [SECTION] MISC HELPERS/UTILITIES (Color functions)
3324// Note: The Convert functions are early design which are not consistent with other API.
3325//-----------------------------------------------------------------------------
3326
3327IMGUI_API ImU32 ImAlphaBlendColors(ImU32 col_a, ImU32 col_b)
3328{
3329 float t = ((col_b >> IM_COL32_A_SHIFT) & 0xFF) / 255.f;
3330 int r = ImLerp((int)(col_a >> IM_COL32_R_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_R_SHIFT) & 0xFF, t);
3331 int g = ImLerp((int)(col_a >> IM_COL32_G_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_G_SHIFT) & 0xFF, t);
3332 int b = ImLerp((int)(col_a >> IM_COL32_B_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_B_SHIFT) & 0xFF, t);
3333 return IM_COL32(r, g, b, 0xFF);
3334}
3335
3336ImVec4 ImGui::ColorConvertU32ToFloat4(ImU32 in)
3337{
3338 float s = 1.0f / 255.0f;
3339 return ImVec4(((in >> IM_COL32_R_SHIFT) & 0xFF) * s, ((in >> IM_COL32_G_SHIFT) & 0xFF) * s,
3340 ((in >> IM_COL32_B_SHIFT) & 0xFF) * s, ((in >> IM_COL32_A_SHIFT) & 0xFF) * s);
3341}
3342
3343ImU32 ImGui::ColorConvertFloat4ToU32(const ImVec4 &in)
3344{
3345 ImU32 out;
3346 out = ((ImU32)IM_F32_TO_INT8_SAT(in.x)) << IM_COL32_R_SHIFT;
3347 out |= ((ImU32)IM_F32_TO_INT8_SAT(in.y)) << IM_COL32_G_SHIFT;
3348 out |= ((ImU32)IM_F32_TO_INT8_SAT(in.z)) << IM_COL32_B_SHIFT;
3349 out |= ((ImU32)IM_F32_TO_INT8_SAT(in.w)) << IM_COL32_A_SHIFT;
3350 return out;
3351}
3352
3353// Convert rgb floats ([0-1],[0-1],[0-1]) to hsv floats ([0-1],[0-1],[0-1]), from Foley & van Dam p592
3354// Optimized http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv
3355void ImGui::ColorConvertRGBtoHSV(float r, float g, float b, float &out_h, float &out_s, float &out_v)
3356{
3357 float K = 0.f;
3358 if (g < b)
3359 {
3360 ImSwap(g, b);
3361 K = -1.f;
3362 }
3363 if (r < g)
3364 {
3365 ImSwap(r, g);
3366 K = -2.f / 6.f - K;
3367 }
3368
3369 const float chroma = r - (g < b ? g : b);
3370 out_h = ImFabs(K + (g - b) / (6.f * chroma + 1e-20f));
3371 out_s = chroma / (r + 1e-20f);
3372 out_v = r;
3373}
3374
3375// Convert hsv floats ([0-1],[0-1],[0-1]) to rgb floats ([0-1],[0-1],[0-1]), from Foley & van Dam p593
3376// also http://en.wikipedia.org/wiki/HSL_and_HSV
3377void ImGui::ColorConvertHSVtoRGB(float h, float s, float v, float &out_r, float &out_g, float &out_b)
3378{
3379 if (s == 0.0f)
3380 {
3381 // gray
3382 out_r = out_g = out_b = v;
3383 return;
3384 }
3385
3386 h = ImFmod(h, 1.0f) / (60.0f / 360.0f);
3387 int i = (int)h;
3388 float f = h - (float)i;
3389 float p = v * (1.0f - s);
3390 float q = v * (1.0f - s * f);
3391 float t = v * (1.0f - s * (1.0f - f));
3392
3393 switch (i)
3394 {
3395 case 0:
3396 out_r = v;
3397 out_g = t;
3398 out_b = p;
3399 break;
3400 case 1:
3401 out_r = q;
3402 out_g = v;
3403 out_b = p;
3404 break;
3405 case 2:
3406 out_r = p;
3407 out_g = v;
3408 out_b = t;
3409 break;
3410 case 3:
3411 out_r = p;
3412 out_g = q;
3413 out_b = v;
3414 break;
3415 case 4:
3416 out_r = t;
3417 out_g = p;
3418 out_b = v;
3419 break;
3420 case 5:
3421 default:
3422 out_r = v;
3423 out_g = p;
3424 out_b = q;
3425 break;
3426 }
3427}
3428
3429//-----------------------------------------------------------------------------
3430// [SECTION] ImGuiStorage
3431// Helper: Key->value storage
3432//-----------------------------------------------------------------------------
3433
3434// std::lower_bound but without the bullshit
3435ImGuiStoragePair *ImLowerBound(ImGuiStoragePair *in_begin, ImGuiStoragePair *in_end, ImGuiID key)
3436{
3437 ImGuiStoragePair *in_p = in_begin;
3438 for (size_t count = (size_t)(in_end - in_p); count > 0;)
3439 {
3440 size_t count2 = count >> 1;
3441 ImGuiStoragePair *mid = in_p + count2;
3442 if (mid->key < key)
3443 {
3444 in_p = ++mid;
3445 count -= count2 + 1;
3446 }
3447 else
3448 {
3449 count = count2;
3450 }
3451 }
3452 return in_p;
3453}
3454
3455IM_MSVC_RUNTIME_CHECKS_OFF
3456static int IMGUI_CDECL PairComparerByID(const void *lhs, const void *rhs)
3457{
3458 // We can't just do a subtraction because qsort uses signed integers and subtracting our ID doesn't play well with
3459 // that.
3460 ImGuiID lhs_v = ((const ImGuiStoragePair *)lhs)->key;
3461 ImGuiID rhs_v = ((const ImGuiStoragePair *)rhs)->key;
3462 return (lhs_v > rhs_v ? +1 : lhs_v < rhs_v ? -1 : 0);
3463}
3464
3465// For quicker full rebuild of a storage (instead of an incremental one), you may add all your contents and then sort
3466// once.
3467void ImGuiStorage::BuildSortByKey()
3468{
3469 ImQsort(Data.Data, (size_t)Data.Size, sizeof(ImGuiStoragePair), PairComparerByID);
3470}
3471
3472int ImGuiStorage::GetInt(ImGuiID key, int default_val) const
3473{
3474 ImGuiStoragePair *it = ImLowerBound(const_cast<ImGuiStoragePair *>(Data.Data),
3475 const_cast<ImGuiStoragePair *>(Data.Data + Data.Size), key);
3476 if (it == Data.Data + Data.Size || it->key != key)
3477 return default_val;
3478 return it->val_i;
3479}
3480
3481bool ImGuiStorage::GetBool(ImGuiID key, bool default_val) const
3482{
3483 return GetInt(key, default_val ? 1 : 0) != 0;
3484}
3485
3486float ImGuiStorage::GetFloat(ImGuiID key, float default_val) const
3487{
3488 ImGuiStoragePair *it = ImLowerBound(const_cast<ImGuiStoragePair *>(Data.Data),
3489 const_cast<ImGuiStoragePair *>(Data.Data + Data.Size), key);
3490 if (it == Data.Data + Data.Size || it->key != key)
3491 return default_val;
3492 return it->val_f;
3493}
3494
3495void *ImGuiStorage::GetVoidPtr(ImGuiID key) const
3496{
3497 ImGuiStoragePair *it = ImLowerBound(const_cast<ImGuiStoragePair *>(Data.Data),
3498 const_cast<ImGuiStoragePair *>(Data.Data + Data.Size), key);
3499 if (it == Data.Data + Data.Size || it->key != key)
3500 return NULL;
3501 return it->val_p;
3502}
3503
3504// References are only valid until a new value is added to the storage. Calling a Set***() function or a Get***Ref()
3505// function invalidates the pointer.
3506int *ImGuiStorage::GetIntRef(ImGuiID key, int default_val)
3507{
3508 ImGuiStoragePair *it = ImLowerBound(Data.Data, Data.Data + Data.Size, key);
3509 if (it == Data.Data + Data.Size || it->key != key)
3510 it = Data.insert(it, ImGuiStoragePair(key, default_val));
3511 return &it->val_i;
3512}
3513
3514bool *ImGuiStorage::GetBoolRef(ImGuiID key, bool default_val)
3515{
3516 return (bool *)GetIntRef(key, default_val ? 1 : 0);
3517}
3518
3519float *ImGuiStorage::GetFloatRef(ImGuiID key, float default_val)
3520{
3521 ImGuiStoragePair *it = ImLowerBound(Data.Data, Data.Data + Data.Size, key);
3522 if (it == Data.Data + Data.Size || it->key != key)
3523 it = Data.insert(it, ImGuiStoragePair(key, default_val));
3524 return &it->val_f;
3525}
3526
3527void **ImGuiStorage::GetVoidPtrRef(ImGuiID key, void *default_val)
3528{
3529 ImGuiStoragePair *it = ImLowerBound(Data.Data, Data.Data + Data.Size, key);
3530 if (it == Data.Data + Data.Size || it->key != key)
3531 it = Data.insert(it, ImGuiStoragePair(key, default_val));
3532 return &it->val_p;
3533}
3534
3535// FIXME-OPT: Need a way to reuse the result of lower_bound when doing GetInt()/SetInt() - not too bad because it only
3536// happens on explicit interaction (maximum one a frame)
3537void ImGuiStorage::SetInt(ImGuiID key, int val)
3538{
3539 ImGuiStoragePair *it = ImLowerBound(Data.Data, Data.Data + Data.Size, key);
3540 if (it == Data.Data + Data.Size || it->key != key)
3541 Data.insert(it, ImGuiStoragePair(key, val));
3542 else
3543 it->val_i = val;
3544}
3545
3546void ImGuiStorage::SetBool(ImGuiID key, bool val)
3547{
3548 SetInt(key, val ? 1 : 0);
3549}
3550
3551void ImGuiStorage::SetFloat(ImGuiID key, float val)
3552{
3553 ImGuiStoragePair *it = ImLowerBound(Data.Data, Data.Data + Data.Size, key);
3554 if (it == Data.Data + Data.Size || it->key != key)
3555 Data.insert(it, ImGuiStoragePair(key, val));
3556 else
3557 it->val_f = val;
3558}
3559
3560void ImGuiStorage::SetVoidPtr(ImGuiID key, void *val)
3561{
3562 ImGuiStoragePair *it = ImLowerBound(Data.Data, Data.Data + Data.Size, key);
3563 if (it == Data.Data + Data.Size || it->key != key)
3564 Data.insert(it, ImGuiStoragePair(key, val));
3565 else
3566 it->val_p = val;
3567}
3568
3569void ImGuiStorage::SetAllInt(int v)
3570{
3571 for (int i = 0; i < Data.Size; i++)
3572 Data[i].val_i = v;
3573}
3574IM_MSVC_RUNTIME_CHECKS_RESTORE
3575
3576//-----------------------------------------------------------------------------
3577// [SECTION] ImGuiTextFilter
3578//-----------------------------------------------------------------------------
3579
3580// Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]"
3581ImGuiTextFilter::ImGuiTextFilter(const char *default_filter) //-V1077
3582{
3583 InputBuf[0] = 0;
3584 CountGrep = 0;
3585 if (default_filter)
3586 {
3587 ImStrncpy(InputBuf, default_filter, IM_ARRAYSIZE(InputBuf));
3588 Build();
3589 }
3590}
3591
3592bool ImGuiTextFilter::Draw(const char *label, float width)
3593{
3594 if (width != 0.0f)
3595 ImGui::SetNextItemWidth(width);
3596 bool value_changed = ImGui::InputText(label, InputBuf, IM_ARRAYSIZE(InputBuf));
3597 if (value_changed)
3598 Build();
3599 return value_changed;
3600}
3601
3602void ImGuiTextFilter::ImGuiTextRange::split(char separator, ImVector<ImGuiTextRange> *out) const
3603{
3604 out->resize(0);
3605 const char *wb = b;
3606 const char *we = wb;
3607 while (we < e)
3608 {
3609 if (*we == separator)
3610 {
3611 out->push_back(ImGuiTextRange(wb, we));
3612 wb = we + 1;
3613 }
3614 we++;
3615 }
3616 if (wb != we)
3617 out->push_back(ImGuiTextRange(wb, we));
3618}
3619
3620void ImGuiTextFilter::Build()
3621{
3622 Filters.resize(0);
3623 ImGuiTextRange input_range(InputBuf, InputBuf + ImStrlen(InputBuf));
3624 input_range.split(',', &Filters);
3625
3626 CountGrep = 0;
3627 for (ImGuiTextRange &f : Filters)
3628 {
3629 while (f.b < f.e && ImCharIsBlankA(f.b[0]))
3630 f.b++;
3631 while (f.e > f.b && ImCharIsBlankA(f.e[-1]))
3632 f.e--;
3633 if (f.empty())
3634 continue;
3635 if (f.b[0] != '-')
3636 CountGrep += 1;
3637 }
3638}
3639
3640bool ImGuiTextFilter::PassFilter(const char *text, const char *text_end) const
3641{
3642 if (Filters.Size == 0)
3643 return true;
3644
3645 if (text == NULL)
3646 text = text_end = "";
3647
3648 for (const ImGuiTextRange &f : Filters)
3649 {
3650 if (f.b == f.e)
3651 continue;
3652 if (f.b[0] == '-')
3653 {
3654 // Subtract
3655 if (ImStristr(text, text_end, f.b + 1, f.e) != NULL)
3656 return false;
3657 }
3658 else
3659 {
3660 // Grep
3661 if (ImStristr(text, text_end, f.b, f.e) != NULL)
3662 return true;
3663 }
3664 }
3665
3666 // Implicit * grep
3667 if (CountGrep == 0)
3668 return true;
3669
3670 return false;
3671}
3672
3673//-----------------------------------------------------------------------------
3674// [SECTION] ImGuiTextBuffer, ImGuiTextIndex
3675//-----------------------------------------------------------------------------
3676
3677// On some platform vsnprintf() takes va_list by reference and modifies it.
3678// va_copy is the 'correct' way to copy a va_list but Visual Studio prior to 2013 doesn't have it.
3679#ifndef va_copy
3680#if defined(__GNUC__) || defined(__clang__)
3681#define va_copy(dest, src) __builtin_va_copy(dest, src)
3682#else
3683#define va_copy(dest, src) (dest = src)
3684#endif
3685#endif
3686
3687char ImGuiTextBuffer::EmptyString[1] = {0};
3688
3689void ImGuiTextBuffer::append(const char *str, const char *str_end)
3690{
3691 int len = str_end ? (int)(str_end - str) : (int)ImStrlen(str);
3692
3693 // Add zero-terminator the first time
3694 const int write_off = (Buf.Size != 0) ? Buf.Size : 1;
3695 const int needed_sz = write_off + len;
3696 if (write_off + len >= Buf.Capacity)
3697 {
3698 int new_capacity = Buf.Capacity * 2;
3699 Buf.reserve(needed_sz > new_capacity ? needed_sz : new_capacity);
3700 }
3701
3702 Buf.resize(needed_sz);
3703 memcpy(&Buf[write_off - 1], str, (size_t)len);
3704 Buf[write_off - 1 + len] = 0;
3705}
3706
3707void ImGuiTextBuffer::appendf(const char *fmt, ...)
3708{
3709 va_list args;
3710 va_start(args, fmt);
3711 appendfv(fmt, args);
3712 va_end(args);
3713}
3714
3715// Helper: Text buffer for logging/accumulating text
3716void ImGuiTextBuffer::appendfv(const char *fmt, va_list args)
3717{
3718 va_list args_copy;
3719 va_copy(args_copy, args);
3720
3721 int len = ImFormatStringV(NULL, 0, fmt,
3722 args); // FIXME-OPT: could do a first pass write attempt, likely successful on first pass.
3723 if (len <= 0)
3724 {
3725 va_end(args_copy);
3726 return;
3727 }
3728
3729 // Add zero-terminator the first time
3730 const int write_off = (Buf.Size != 0) ? Buf.Size : 1;
3731 const int needed_sz = write_off + len;
3732 if (write_off + len >= Buf.Capacity)
3733 {
3734 int new_capacity = Buf.Capacity * 2;
3735 Buf.reserve(needed_sz > new_capacity ? needed_sz : new_capacity);
3736 }
3737
3738 Buf.resize(needed_sz);
3739 ImFormatStringV(&Buf[write_off - 1], (size_t)len + 1, fmt, args_copy);
3740 va_end(args_copy);
3741}
3742
3743void ImGuiTextIndex::append(const char *base, int old_size, int new_size)
3744{
3745 IM_ASSERT(old_size >= 0 && new_size >= old_size && new_size >= EndOffset);
3746 if (old_size == new_size)
3747 return;
3748 if (EndOffset == 0 || base[EndOffset - 1] == '\n')
3749 LineOffsets.push_back(EndOffset);
3750 const char *base_end = base + new_size;
3751 for (const char *p = base + old_size; (p = (const char *)ImMemchr(p, '\n', base_end - p)) != 0;)
3752 if (++p < base_end) // Don't push a trailing offset on last \n
3753 LineOffsets.push_back((int)(intptr_t)(p - base));
3754 EndOffset = ImMax(EndOffset, new_size);
3755}
3756
3757//-----------------------------------------------------------------------------
3758// [SECTION] ImGuiListClipper
3759//-----------------------------------------------------------------------------
3760
3761// FIXME-TABLE: This prevents us from using ImGuiListClipper _inside_ a table cell.
3762// The problem we have is that without a Begin/End scheme for rows using the clipper is ambiguous.
3763static bool GetSkipItemForListClipping()
3764{
3765 ImGuiContext &g = *GImGui;
3766 return (g.CurrentTable ? g.CurrentTable->HostSkipItems : g.CurrentWindow->SkipItems);
3767}
3768
3769static void ImGuiListClipper_SortAndFuseRanges(ImVector<ImGuiListClipperRange> &ranges, int offset = 0)
3770{
3771 if (ranges.Size - offset <= 1)
3772 return;
3773
3774 // Helper to order ranges and fuse them together if possible (bubble sort is fine as we are only sorting 2-3
3775 // entries)
3776 for (int sort_end = ranges.Size - offset - 1; sort_end > 0; --sort_end)
3777 for (int i = offset; i < sort_end + offset; ++i)
3778 if (ranges[i].Min > ranges[i + 1].Min)
3779 ImSwap(ranges[i], ranges[i + 1]);
3780
3781 // Now fuse ranges together as much as possible.
3782 for (int i = 1 + offset; i < ranges.Size; i++)
3783 {
3784 IM_ASSERT(!ranges[i].PosToIndexConvert && !ranges[i - 1].PosToIndexConvert);
3785 if (ranges[i - 1].Max < ranges[i].Min)
3786 continue;
3787 ranges[i - 1].Min = ImMin(ranges[i - 1].Min, ranges[i].Min);
3788 ranges[i - 1].Max = ImMax(ranges[i - 1].Max, ranges[i].Max);
3789 ranges.erase(ranges.Data + i);
3790 i--;
3791 }
3792}
3793
3794static void ImGuiListClipper_SeekCursorAndSetupPrevLine(float pos_y, float line_height)
3795{
3796 // Set cursor position and a few other things so that SetScrollHereY() and Columns() can work when seeking cursor.
3797 // FIXME: It is problematic that we have to do that here, because custom/equivalent end-user code would stumble on
3798 // the same issue. The clipper should probably have a final step to display the last item in a regular manner, maybe
3799 // with an opt-out flag for data sets which may have costly seek?
3800 ImGuiContext &g = *GImGui;
3801 ImGuiWindow *window = g.CurrentWindow;
3802 float off_y = pos_y - window->DC.CursorPos.y;
3803 window->DC.CursorPos.y = pos_y;
3804 window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, pos_y - g.Style.ItemSpacing.y);
3805 window->DC.CursorPosPrevLine.y =
3806 window->DC.CursorPos.y - line_height; // Setting those fields so that SetScrollHereY() can properly function
3807 // after the end of our clipper usage.
3808 window->DC.PrevLineSize.y =
3809 (line_height -
3810 g.Style.ItemSpacing
3811 .y); // If we end up needing more accurate data (to e.g. use SameLine) we may as well make the clipper have
3812 // a fourth step to let user process and display the last item in their list.
3813 if (ImGuiOldColumns *columns = window->DC.CurrentColumns)
3814 columns->LineMinY = window->DC.CursorPos.y; // Setting this so that cell Y position are set properly
3815 if (ImGuiTable *table = g.CurrentTable)
3816 {
3817 if (table->IsInsideRow)
3818 ImGui::TableEndRow(table);
3819 table->RowPosY2 = window->DC.CursorPos.y;
3820 const int row_increase = (int)((off_y / line_height) + 0.5f);
3821 // table->CurrentRow += row_increase; // Can't do without fixing TableEndRow()
3822 table->RowBgColorCounter += row_increase;
3823 }
3824}
3825
3826ImGuiListClipper::ImGuiListClipper()
3827{
3828 memset(this, 0, sizeof(*this));
3829}
3830
3831ImGuiListClipper::~ImGuiListClipper()
3832{
3833 End();
3834}
3835
3836void ImGuiListClipper::Begin(int items_count, float items_height)
3837{
3838 if (Ctx == NULL)
3839 Ctx = ImGui::GetCurrentContext();
3840
3841 ImGuiContext &g = *Ctx;
3842 ImGuiWindow *window = g.CurrentWindow;
3843 IMGUI_DEBUG_LOG_CLIPPER("Clipper: Begin(%d,%.2f) in '%s'\n", items_count, items_height, window->Name);
3844
3845 if (ImGuiTable *table = g.CurrentTable)
3846 if (table->IsInsideRow)
3847 ImGui::TableEndRow(table);
3848
3849 StartPosY = window->DC.CursorPos.y;
3850 ItemsHeight = items_height;
3851 ItemsCount = items_count;
3852 DisplayStart = -1;
3853 DisplayEnd = 0;
3854
3855 // Acquire temporary buffer
3856 if (++g.ClipperTempDataStacked > g.ClipperTempData.Size)
3857 g.ClipperTempData.resize(g.ClipperTempDataStacked, ImGuiListClipperData());
3858 ImGuiListClipperData *data = &g.ClipperTempData[g.ClipperTempDataStacked - 1];
3859 data->Reset(this);
3860 data->LossynessOffset = window->DC.CursorStartPosLossyness.y;
3861 TempData = data;
3862 StartSeekOffsetY = data->LossynessOffset;
3863}
3864
3865void ImGuiListClipper::End()
3866{
3867 if (ImGuiListClipperData *data = (ImGuiListClipperData *)TempData)
3868 {
3869 // In theory here we should assert that we are already at the right position, but it seems saner to just seek at
3870 // the end and not assert/crash the user.
3871 ImGuiContext &g = *Ctx;
3872 IMGUI_DEBUG_LOG_CLIPPER("Clipper: End() in '%s'\n", g.CurrentWindow->Name);
3873 if (ItemsCount >= 0 && ItemsCount < INT_MAX && DisplayStart >= 0)
3874 SeekCursorForItem(ItemsCount);
3875
3876 // Restore temporary buffer and fix back pointers which may be invalidated when nesting
3877 IM_ASSERT(data->ListClipper == this);
3878 data->StepNo = data->Ranges.Size;
3879 if (--g.ClipperTempDataStacked > 0)
3880 {
3881 data = &g.ClipperTempData[g.ClipperTempDataStacked - 1];
3882 data->ListClipper->TempData = data;
3883 }
3884 TempData = NULL;
3885 }
3886 ItemsCount = -1;
3887}
3888
3889void ImGuiListClipper::IncludeItemsByIndex(int item_begin, int item_end)
3890{
3891 ImGuiListClipperData *data = (ImGuiListClipperData *)TempData;
3892 IM_ASSERT(DisplayStart < 0); // Only allowed after Begin() and if there has not been a specified range yet.
3893 IM_ASSERT(item_begin <= item_end);
3894 if (item_begin < item_end)
3895 data->Ranges.push_back(ImGuiListClipperRange::FromIndices(item_begin, item_end));
3896}
3897
3898// This is already called while stepping.
3899// The ONLY reason you may want to call this is if you passed INT_MAX to ImGuiListClipper::Begin() because you couldn't
3900// step item count beforehand.
3901void ImGuiListClipper::SeekCursorForItem(int item_n)
3902{
3903 // - Perform the add and multiply with double to allow seeking through larger ranges.
3904 // - StartPosY starts from ItemsFrozen, by adding SeekOffsetY we generally cancel that out (SeekOffsetY ==
3905 // LossynessOffset - ItemsFrozen * ItemsHeight).
3906 // - The reason we store SeekOffsetY instead of inferring it, is because we want to allow user to perform Seek after
3907 // the last step, where ImGuiListClipperData is already done.
3908 float pos_y = (float)((double)StartPosY + StartSeekOffsetY + (double)item_n * ItemsHeight);
3909 ImGuiListClipper_SeekCursorAndSetupPrevLine(pos_y, ItemsHeight);
3910}
3911
3912static bool ImGuiListClipper_StepInternal(ImGuiListClipper *clipper)
3913{
3914 ImGuiContext &g = *clipper->Ctx;
3915 ImGuiWindow *window = g.CurrentWindow;
3916 ImGuiListClipperData *data = (ImGuiListClipperData *)clipper->TempData;
3917 IM_ASSERT(data != NULL && "Called ImGuiListClipper::Step() too many times, or before ImGuiListClipper::Begin() ?");
3918
3919 ImGuiTable *table = g.CurrentTable;
3920 if (table && table->IsInsideRow)
3921 ImGui::TableEndRow(table);
3922
3923 // No items
3924 if (clipper->ItemsCount == 0 || GetSkipItemForListClipping())
3925 return false;
3926
3927 // While we are in frozen row state, keep displaying items one by one, unclipped
3928 // FIXME: Could be stored as a table-agnostic state.
3929 if (data->StepNo == 0 && table != NULL && !table->IsUnfrozenRows)
3930 {
3931 clipper->DisplayStart = data->ItemsFrozen;
3932 clipper->DisplayEnd = ImMin(data->ItemsFrozen + 1, clipper->ItemsCount);
3933 if (clipper->DisplayStart < clipper->DisplayEnd)
3934 data->ItemsFrozen++;
3935 return true;
3936 }
3937
3938 // Step 0: Let you process the first element (regardless of it being visible or not, so we can measure the element
3939 // height)
3940 bool calc_clipping = false;
3941 if (data->StepNo == 0)
3942 {
3943 clipper->StartPosY = window->DC.CursorPos.y;
3944 if (clipper->ItemsHeight <= 0.0f)
3945 {
3946 // Submit the first item (or range) so we can measure its height (generally the first range is 0..1)
3947 data->Ranges.push_front(ImGuiListClipperRange::FromIndices(data->ItemsFrozen, data->ItemsFrozen + 1));
3948 clipper->DisplayStart = ImMax(data->Ranges[0].Min, data->ItemsFrozen);
3949 clipper->DisplayEnd = ImMin(data->Ranges[0].Max, clipper->ItemsCount);
3950 data->StepNo = 1;
3951 return true;
3952 }
3953 calc_clipping = true; // If on the first step with known item height, calculate clipping.
3954 }
3955
3956 // Step 1: Let the clipper infer height from first range
3957 if (clipper->ItemsHeight <= 0.0f)
3958 {
3959 IM_ASSERT(data->StepNo == 1);
3960 if (table)
3961 IM_ASSERT(table->RowPosY1 == clipper->StartPosY && table->RowPosY2 == window->DC.CursorPos.y);
3962
3963 clipper->ItemsHeight =
3964 (window->DC.CursorPos.y - clipper->StartPosY) / (float)(clipper->DisplayEnd - clipper->DisplayStart);
3965 bool affected_by_floating_point_precision = ImIsFloatAboveGuaranteedIntegerPrecision(clipper->StartPosY) ||
3966 ImIsFloatAboveGuaranteedIntegerPrecision(window->DC.CursorPos.y);
3967 if (affected_by_floating_point_precision)
3968 clipper->ItemsHeight = window->DC.PrevLineSize.y +
3969 g.Style.ItemSpacing.y; // FIXME: Technically wouldn't allow multi-line entries.
3970 if (clipper->ItemsHeight == 0.0f &&
3971 clipper->ItemsCount == INT_MAX) // Accept that no item have been submitted if in indeterminate mode.
3972 return false;
3973 IM_ASSERT(clipper->ItemsHeight > 0.0f &&
3974 "Unable to calculate item height! First item hasn't moved the cursor vertically!");
3975 calc_clipping = true; // If item height had to be calculated, calculate clipping afterwards.
3976 }
3977
3978 // Step 0 or 1: Calculate the actual ranges of visible elements.
3979 const int already_submitted = clipper->DisplayEnd;
3980 if (calc_clipping)
3981 {
3982 // Record seek offset, this is so ImGuiListClipper::Seek() can be called after ImGuiListClipperData is done
3983 clipper->StartSeekOffsetY = (double)data->LossynessOffset - data->ItemsFrozen * (double)clipper->ItemsHeight;
3984
3985 if (g.LogEnabled)
3986 {
3987 // If logging is active, do not perform any clipping
3988 data->Ranges.push_back(ImGuiListClipperRange::FromIndices(0, clipper->ItemsCount));
3989 }
3990 else
3991 {
3992 // Add range selected to be included for navigation
3993 const bool is_nav_request =
3994 (g.NavMoveScoringItems && g.NavWindow && g.NavWindow->RootWindowForNav == window->RootWindowForNav);
3995 if (is_nav_request)
3996 data->Ranges.push_back(ImGuiListClipperRange::FromPositions(g.NavScoringNoClipRect.Min.y,
3997 g.NavScoringNoClipRect.Max.y, 0, 0));
3998 if (is_nav_request && (g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) && g.NavTabbingDir == -1)
3999 data->Ranges.push_back(
4000 ImGuiListClipperRange::FromIndices(clipper->ItemsCount - 1, clipper->ItemsCount));
4001
4002 // Add focused/active item
4003 ImRect nav_rect_abs = ImGui::WindowRectRelToAbs(window, window->NavRectRel[0]);
4004 if (g.NavId != 0 && window->NavLastIds[0] == g.NavId)
4005 data->Ranges.push_back(
4006 ImGuiListClipperRange::FromPositions(nav_rect_abs.Min.y, nav_rect_abs.Max.y, 0, 0));
4007
4008 // Add visible range
4009 float min_y = window->ClipRect.Min.y;
4010 float max_y = window->ClipRect.Max.y;
4011
4012 // Add box selection range
4013 ImGuiBoxSelectState *bs = &g.BoxSelectState;
4014 if (bs->IsActive && bs->Window == window)
4015 {
4016 // FIXME: Selectable() use of half-ItemSpacing isn't consistent in matter of layout, as ItemAdd(bb)
4017 // stray above ItemSize()'s CursorPos. RangeSelect's BoxSelect relies on comparing overlap of previous
4018 // and current rectangle and is sensitive to that. As a workaround we currently half ItemSpacing worth
4019 // on each side.
4020 min_y -= g.Style.ItemSpacing.y;
4021 max_y += g.Style.ItemSpacing.y;
4022
4023 // Box-select on 2D area requires different clipping.
4024 if (bs->UnclipMode)
4025 data->Ranges.push_back(
4026 ImGuiListClipperRange::FromPositions(bs->UnclipRect.Min.y, bs->UnclipRect.Max.y, 0, 0));
4027 }
4028
4029 const int off_min = (is_nav_request && g.NavMoveClipDir == ImGuiDir_Up) ? -1 : 0;
4030 const int off_max = (is_nav_request && g.NavMoveClipDir == ImGuiDir_Down) ? 1 : 0;
4031 data->Ranges.push_back(ImGuiListClipperRange::FromPositions(min_y, max_y, off_min, off_max));
4032 }
4033
4034 // Convert position ranges to item index ranges
4035 // - Very important: when a starting position is after our maximum item, we set Min to (ItemsCount - 1). This
4036 // allows us to handle most forms of wrapping.
4037 // - Due to how Selectable extra padding they tend to be "unaligned" with exact unit in the item list,
4038 // which with the flooring/ceiling tend to lead to 2 items instead of one being submitted.
4039 for (ImGuiListClipperRange &range : data->Ranges)
4040 if (range.PosToIndexConvert)
4041 {
4042 int m1 =
4043 (int)(((double)range.Min - window->DC.CursorPos.y - data->LossynessOffset) / clipper->ItemsHeight);
4044 int m2 = (int)((((double)range.Max - window->DC.CursorPos.y - data->LossynessOffset) /
4045 clipper->ItemsHeight) +
4046 0.999999f);
4047 range.Min = ImClamp(already_submitted + m1 + range.PosToIndexOffsetMin, already_submitted,
4048 clipper->ItemsCount - 1);
4049 range.Max =
4050 ImClamp(already_submitted + m2 + range.PosToIndexOffsetMax, range.Min + 1, clipper->ItemsCount);
4051 range.PosToIndexConvert = false;
4052 }
4053 ImGuiListClipper_SortAndFuseRanges(data->Ranges, data->StepNo);
4054 }
4055
4056 // Step 0+ (if item height is given in advance) or 1+: Display the next range in line.
4057 while (data->StepNo < data->Ranges.Size)
4058 {
4059 clipper->DisplayStart = ImMax(data->Ranges[data->StepNo].Min, already_submitted);
4060 clipper->DisplayEnd = ImMin(data->Ranges[data->StepNo].Max, clipper->ItemsCount);
4061 data->StepNo++;
4062 if (clipper->DisplayStart >= clipper->DisplayEnd)
4063 continue;
4064 if (clipper->DisplayStart > already_submitted)
4065 clipper->SeekCursorForItem(clipper->DisplayStart);
4066 return true;
4067 }
4068
4069 // After the last step: Let the clipper validate that we have reached the expected Y position (corresponding to
4070 // element DisplayEnd), Advance the cursor to the end of the list and then returns 'false' to end the loop.
4071 if (clipper->ItemsCount < INT_MAX)
4072 clipper->SeekCursorForItem(clipper->ItemsCount);
4073
4074 return false;
4075}
4076
4077bool ImGuiListClipper::Step()
4078{
4079 ImGuiContext &g = *Ctx;
4080 bool need_items_height = (ItemsHeight <= 0.0f);
4081 bool ret = ImGuiListClipper_StepInternal(this);
4082 if (ret && (DisplayStart >= DisplayEnd))
4083 ret = false;
4084 if (g.CurrentTable && g.CurrentTable->IsUnfrozenRows == false)
4085 IMGUI_DEBUG_LOG_CLIPPER("Clipper: Step(): inside frozen table row.\n");
4086 if (need_items_height && ItemsHeight > 0.0f)
4087 IMGUI_DEBUG_LOG_CLIPPER("Clipper: Step(): computed ItemsHeight: %.2f.\n", ItemsHeight);
4088 if (ret)
4089 {
4090 IMGUI_DEBUG_LOG_CLIPPER("Clipper: Step(): display %d to %d.\n", DisplayStart, DisplayEnd);
4091 }
4092 else
4093 {
4094 IMGUI_DEBUG_LOG_CLIPPER("Clipper: Step(): End.\n");
4095 End();
4096 }
4097 return ret;
4098}
4099
4100//-----------------------------------------------------------------------------
4101// [SECTION] STYLING
4102//-----------------------------------------------------------------------------
4103
4104ImGuiStyle &ImGui::GetStyle()
4105{
4106 IM_ASSERT(GImGui != NULL &&
4107 "No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext() ?");
4108 return GImGui->Style;
4109}
4110
4111ImU32 ImGui::GetColorU32(ImGuiCol idx, float alpha_mul)
4112{
4113 ImGuiStyle &style = GImGui->Style;
4114 ImVec4 c = style.Colors[idx];
4115 c.w *= style.Alpha * alpha_mul;
4116 return ColorConvertFloat4ToU32(c);
4117}
4118
4119ImU32 ImGui::GetColorU32(const ImVec4 &col)
4120{
4121 ImGuiStyle &style = GImGui->Style;
4122 ImVec4 c = col;
4123 c.w *= style.Alpha;
4124 return ColorConvertFloat4ToU32(c);
4125}
4126
4127const ImVec4 &ImGui::GetStyleColorVec4(ImGuiCol idx)
4128{
4129 ImGuiStyle &style = GImGui->Style;
4130 return style.Colors[idx];
4131}
4132
4133ImU32 ImGui::GetColorU32(ImU32 col, float alpha_mul)
4134{
4135 ImGuiStyle &style = GImGui->Style;
4136 alpha_mul *= style.Alpha;
4137 if (alpha_mul >= 1.0f)
4138 return col;
4139 ImU32 a = (col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT;
4140 a = (ImU32)(a * alpha_mul); // We don't need to clamp 0..255 because alpha is in 0..1 range.
4141 return (col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT);
4142}
4143
4144// FIXME: This may incur a round-trip (if the end user got their data from a float4) but eventually we aim to store the
4145// in-flight colors as ImU32
4146void ImGui::PushStyleColor(ImGuiCol idx, ImU32 col)
4147{
4148 ImGuiContext &g = *GImGui;
4149 ImGuiColorMod backup;
4150 backup.Col = idx;
4151 backup.BackupValue = g.Style.Colors[idx];
4152 g.ColorStack.push_back(backup);
4153 if (g.DebugFlashStyleColorIdx != idx)
4154 g.Style.Colors[idx] = ColorConvertU32ToFloat4(col);
4155}
4156
4157void ImGui::PushStyleColor(ImGuiCol idx, const ImVec4 &col)
4158{
4159 ImGuiContext &g = *GImGui;
4160 ImGuiColorMod backup;
4161 backup.Col = idx;
4162 backup.BackupValue = g.Style.Colors[idx];
4163 g.ColorStack.push_back(backup);
4164 if (g.DebugFlashStyleColorIdx != idx)
4165 g.Style.Colors[idx] = col;
4166}
4167
4168void ImGui::PopStyleColor(int count)
4169{
4170 ImGuiContext &g = *GImGui;
4171 if (g.ColorStack.Size < count)
4172 {
4173 IM_ASSERT_USER_ERROR(0, "Calling PopStyleColor() too many times!");
4174 count = g.ColorStack.Size;
4175 }
4176 while (count > 0)
4177 {
4178 ImGuiColorMod &backup = g.ColorStack.back();
4179 g.Style.Colors[backup.Col] = backup.BackupValue;
4180 g.ColorStack.pop_back();
4181 count--;
4182 }
4183}
4184
4185static const ImGuiCol GWindowDockStyleColors[ImGuiWindowDockStyleCol_COUNT] = {
4186 ImGuiCol_Text,
4187 ImGuiCol_TabHovered,
4188 ImGuiCol_Tab,
4189 ImGuiCol_TabSelected,
4190 ImGuiCol_TabSelectedOverline,
4191 ImGuiCol_TabDimmed,
4192 ImGuiCol_TabDimmedSelected,
4193 ImGuiCol_TabDimmedSelectedOverline,
4194};
4195
4196static const ImGuiStyleVarInfo GStyleVarsInfo[] = {
4197 {1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, Alpha)}, // ImGuiStyleVar_Alpha
4198 {1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, DisabledAlpha)}, // ImGuiStyleVar_DisabledAlpha
4199 {2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, WindowPadding)}, // ImGuiStyleVar_WindowPadding
4200 {1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, WindowRounding)}, // ImGuiStyleVar_WindowRounding
4201 {1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, WindowBorderSize)}, // ImGuiStyleVar_WindowBorderSize
4202 {2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, WindowMinSize)}, // ImGuiStyleVar_WindowMinSize
4203 {2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, WindowTitleAlign)}, // ImGuiStyleVar_WindowTitleAlign
4204 {1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ChildRounding)}, // ImGuiStyleVar_ChildRounding
4205 {1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ChildBorderSize)}, // ImGuiStyleVar_ChildBorderSize
4206 {1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, PopupRounding)}, // ImGuiStyleVar_PopupRounding
4207 {1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, PopupBorderSize)}, // ImGuiStyleVar_PopupBorderSize
4208 {2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, FramePadding)}, // ImGuiStyleVar_FramePadding
4209 {1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, FrameRounding)}, // ImGuiStyleVar_FrameRounding
4210 {1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, FrameBorderSize)}, // ImGuiStyleVar_FrameBorderSize
4211 {2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ItemSpacing)}, // ImGuiStyleVar_ItemSpacing
4212 {2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ItemInnerSpacing)}, // ImGuiStyleVar_ItemInnerSpacing
4213 {1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, IndentSpacing)}, // ImGuiStyleVar_IndentSpacing
4214 {2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, CellPadding)}, // ImGuiStyleVar_CellPadding
4215 {1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ScrollbarSize)}, // ImGuiStyleVar_ScrollbarSize
4216 {1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ScrollbarRounding)}, // ImGuiStyleVar_ScrollbarRounding
4217 {1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, GrabMinSize)}, // ImGuiStyleVar_GrabMinSize
4218 {1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, GrabRounding)}, // ImGuiStyleVar_GrabRounding
4219 {1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ImageBorderSize)}, // ImGuiStyleVar_ImageBorderSize
4220 {1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TabRounding)}, // ImGuiStyleVar_TabRounding
4221 {1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TabBorderSize)}, // ImGuiStyleVar_TabBorderSize
4222 {1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TabBarBorderSize)}, // ImGuiStyleVar_TabBarBorderSize
4223 {1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TabBarOverlineSize)}, // ImGuiStyleVar_TabBarOverlineSize
4224 {1, ImGuiDataType_Float,
4225 (ImU32)offsetof(ImGuiStyle, TableAngledHeadersAngle)}, // ImGuiStyleVar_TableAngledHeadersAngle
4226 {2, ImGuiDataType_Float,
4227 (ImU32)offsetof(ImGuiStyle, TableAngledHeadersTextAlign)}, // ImGuiStyleVar_TableAngledHeadersTextAlign
4228 {2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ButtonTextAlign)}, // ImGuiStyleVar_ButtonTextAlign
4229 {2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, SelectableTextAlign)}, // ImGuiStyleVar_SelectableTextAlign
4230 {1, ImGuiDataType_Float,
4231 (ImU32)offsetof(ImGuiStyle, SeparatorTextBorderSize)}, // ImGuiStyleVar_SeparatorTextBorderSize
4232 {2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, SeparatorTextAlign)}, // ImGuiStyleVar_SeparatorTextAlign
4233 {2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, SeparatorTextPadding)}, // ImGuiStyleVar_SeparatorTextPadding
4234 {1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, DockingSeparatorSize)}, // ImGuiStyleVar_DockingSeparatorSize
4235};
4236
4237const ImGuiStyleVarInfo *ImGui::GetStyleVarInfo(ImGuiStyleVar idx)
4238{
4239 IM_ASSERT(idx >= 0 && idx < ImGuiStyleVar_COUNT);
4240 IM_STATIC_ASSERT(IM_ARRAYSIZE(GStyleVarsInfo) == ImGuiStyleVar_COUNT);
4241 return &GStyleVarsInfo[idx];
4242}
4243
4244void ImGui::PushStyleVar(ImGuiStyleVar idx, float val)
4245{
4246 ImGuiContext &g = *GImGui;
4247 const ImGuiStyleVarInfo *var_info = GetStyleVarInfo(idx);
4248 if (var_info->DataType != ImGuiDataType_Float || var_info->Count != 1)
4249 {
4250 IM_ASSERT_USER_ERROR(0, "Calling PushStyleVar() variant with wrong type!");
4251 return;
4252 }
4253 float *pvar = (float *)var_info->GetVarPtr(&g.Style);
4254 g.StyleVarStack.push_back(ImGuiStyleMod(idx, *pvar));
4255 *pvar = val;
4256}
4257
4258void ImGui::PushStyleVarX(ImGuiStyleVar idx, float val_x)
4259{
4260 ImGuiContext &g = *GImGui;
4261 const ImGuiStyleVarInfo *var_info = GetStyleVarInfo(idx);
4262 if (var_info->DataType != ImGuiDataType_Float || var_info->Count != 2)
4263 {
4264 IM_ASSERT_USER_ERROR(0, "Calling PushStyleVar() variant with wrong type!");
4265 return;
4266 }
4267 ImVec2 *pvar = (ImVec2 *)var_info->GetVarPtr(&g.Style);
4268 g.StyleVarStack.push_back(ImGuiStyleMod(idx, *pvar));
4269 pvar->x = val_x;
4270}
4271
4272void ImGui::PushStyleVarY(ImGuiStyleVar idx, float val_y)
4273{
4274 ImGuiContext &g = *GImGui;
4275 const ImGuiStyleVarInfo *var_info = GetStyleVarInfo(idx);
4276 if (var_info->DataType != ImGuiDataType_Float || var_info->Count != 2)
4277 {
4278 IM_ASSERT_USER_ERROR(0, "Calling PushStyleVar() variant with wrong type!");
4279 return;
4280 }
4281 ImVec2 *pvar = (ImVec2 *)var_info->GetVarPtr(&g.Style);
4282 g.StyleVarStack.push_back(ImGuiStyleMod(idx, *pvar));
4283 pvar->y = val_y;
4284}
4285
4286void ImGui::PushStyleVar(ImGuiStyleVar idx, const ImVec2 &val)
4287{
4288 ImGuiContext &g = *GImGui;
4289 const ImGuiStyleVarInfo *var_info = GetStyleVarInfo(idx);
4290 if (var_info->DataType != ImGuiDataType_Float || var_info->Count != 2)
4291 {
4292 IM_ASSERT_USER_ERROR(0, "Calling PushStyleVar() variant with wrong type!");
4293 return;
4294 }
4295 ImVec2 *pvar = (ImVec2 *)var_info->GetVarPtr(&g.Style);
4296 g.StyleVarStack.push_back(ImGuiStyleMod(idx, *pvar));
4297 *pvar = val;
4298}
4299
4300void ImGui::PopStyleVar(int count)
4301{
4302 ImGuiContext &g = *GImGui;
4303 if (g.StyleVarStack.Size < count)
4304 {
4305 IM_ASSERT_USER_ERROR(0, "Calling PopStyleVar() too many times!");
4306 count = g.StyleVarStack.Size;
4307 }
4308 while (count > 0)
4309 {
4310 // We avoid a generic memcpy(data, &backup.Backup.., GDataTypeSize[info->Type] * info->Count), the overhead in
4311 // Debug is not worth it.
4312 ImGuiStyleMod &backup = g.StyleVarStack.back();
4313 const ImGuiStyleVarInfo *var_info = GetStyleVarInfo(backup.VarIdx);
4314 void *data = var_info->GetVarPtr(&g.Style);
4315 if (var_info->DataType == ImGuiDataType_Float && var_info->Count == 1)
4316 {
4317 ((float *)data)[0] = backup.BackupFloat[0];
4318 }
4319 else if (var_info->DataType == ImGuiDataType_Float && var_info->Count == 2)
4320 {
4321 ((float *)data)[0] = backup.BackupFloat[0];
4322 ((float *)data)[1] = backup.BackupFloat[1];
4323 }
4324 g.StyleVarStack.pop_back();
4325 count--;
4326 }
4327}
4328
4329const char *ImGui::GetStyleColorName(ImGuiCol idx)
4330{
4331 // Create switch-case from enum with regexp: ImGuiCol_{.*}, --> case ImGuiCol_\1: return "\1";
4332 switch (idx)
4333 {
4334 case ImGuiCol_Text:
4335 return "Text";
4336 case ImGuiCol_TextDisabled:
4337 return "TextDisabled";
4338 case ImGuiCol_WindowBg:
4339 return "WindowBg";
4340 case ImGuiCol_ChildBg:
4341 return "ChildBg";
4342 case ImGuiCol_PopupBg:
4343 return "PopupBg";
4344 case ImGuiCol_Border:
4345 return "Border";
4346 case ImGuiCol_BorderShadow:
4347 return "BorderShadow";
4348 case ImGuiCol_FrameBg:
4349 return "FrameBg";
4350 case ImGuiCol_FrameBgHovered:
4351 return "FrameBgHovered";
4352 case ImGuiCol_FrameBgActive:
4353 return "FrameBgActive";
4354 case ImGuiCol_TitleBg:
4355 return "TitleBg";
4356 case ImGuiCol_TitleBgActive:
4357 return "TitleBgActive";
4358 case ImGuiCol_TitleBgCollapsed:
4359 return "TitleBgCollapsed";
4360 case ImGuiCol_MenuBarBg:
4361 return "MenuBarBg";
4362 case ImGuiCol_ScrollbarBg:
4363 return "ScrollbarBg";
4364 case ImGuiCol_ScrollbarGrab:
4365 return "ScrollbarGrab";
4366 case ImGuiCol_ScrollbarGrabHovered:
4367 return "ScrollbarGrabHovered";
4368 case ImGuiCol_ScrollbarGrabActive:
4369 return "ScrollbarGrabActive";
4370 case ImGuiCol_CheckMark:
4371 return "CheckMark";
4372 case ImGuiCol_SliderGrab:
4373 return "SliderGrab";
4374 case ImGuiCol_SliderGrabActive:
4375 return "SliderGrabActive";
4376 case ImGuiCol_Button:
4377 return "Button";
4378 case ImGuiCol_ButtonHovered:
4379 return "ButtonHovered";
4380 case ImGuiCol_ButtonActive:
4381 return "ButtonActive";
4382 case ImGuiCol_Header:
4383 return "Header";
4384 case ImGuiCol_HeaderHovered:
4385 return "HeaderHovered";
4386 case ImGuiCol_HeaderActive:
4387 return "HeaderActive";
4388 case ImGuiCol_Separator:
4389 return "Separator";
4390 case ImGuiCol_SeparatorHovered:
4391 return "SeparatorHovered";
4392 case ImGuiCol_SeparatorActive:
4393 return "SeparatorActive";
4394 case ImGuiCol_ResizeGrip:
4395 return "ResizeGrip";
4396 case ImGuiCol_ResizeGripHovered:
4397 return "ResizeGripHovered";
4398 case ImGuiCol_ResizeGripActive:
4399 return "ResizeGripActive";
4400 case ImGuiCol_InputTextCursor:
4401 return "InputTextCursor";
4402 case ImGuiCol_TabHovered:
4403 return "TabHovered";
4404 case ImGuiCol_Tab:
4405 return "Tab";
4406 case ImGuiCol_TabSelected:
4407 return "TabSelected";
4408 case ImGuiCol_TabSelectedOverline:
4409 return "TabSelectedOverline";
4410 case ImGuiCol_TabDimmed:
4411 return "TabDimmed";
4412 case ImGuiCol_TabDimmedSelected:
4413 return "TabDimmedSelected";
4414 case ImGuiCol_TabDimmedSelectedOverline:
4415 return "TabDimmedSelectedOverline";
4416 case ImGuiCol_DockingPreview:
4417 return "DockingPreview";
4418 case ImGuiCol_DockingEmptyBg:
4419 return "DockingEmptyBg";
4420 case ImGuiCol_PlotLines:
4421 return "PlotLines";
4422 case ImGuiCol_PlotLinesHovered:
4423 return "PlotLinesHovered";
4424 case ImGuiCol_PlotHistogram:
4425 return "PlotHistogram";
4426 case ImGuiCol_PlotHistogramHovered:
4427 return "PlotHistogramHovered";
4428 case ImGuiCol_TableHeaderBg:
4429 return "TableHeaderBg";
4430 case ImGuiCol_TableBorderStrong:
4431 return "TableBorderStrong";
4432 case ImGuiCol_TableBorderLight:
4433 return "TableBorderLight";
4434 case ImGuiCol_TableRowBg:
4435 return "TableRowBg";
4436 case ImGuiCol_TableRowBgAlt:
4437 return "TableRowBgAlt";
4438 case ImGuiCol_TextLink:
4439 return "TextLink";
4440 case ImGuiCol_TextSelectedBg:
4441 return "TextSelectedBg";
4442 case ImGuiCol_DragDropTarget:
4443 return "DragDropTarget";
4444 case ImGuiCol_NavCursor:
4445 return "NavCursor";
4446 case ImGuiCol_NavWindowingHighlight:
4447 return "NavWindowingHighlight";
4448 case ImGuiCol_NavWindowingDimBg:
4449 return "NavWindowingDimBg";
4450 case ImGuiCol_ModalWindowDimBg:
4451 return "ModalWindowDimBg";
4452 }
4453 IM_ASSERT(0);
4454 return "Unknown";
4455}
4456
4457//-----------------------------------------------------------------------------
4458// [SECTION] RENDER HELPERS
4459// Some of those (internal) functions are currently quite a legacy mess - their signature and behavior will change,
4460// we need a nicer separation between low-level functions and high-level functions relying on the ImGui context.
4461// Also see imgui_draw.cpp for some more which have been reworked to not rely on ImGui:: context.
4462//-----------------------------------------------------------------------------
4463
4464const char *ImGui::FindRenderedTextEnd(const char *text, const char *text_end)
4465{
4466 const char *text_display_end = text;
4467 if (!text_end)
4468 text_end = (const char *)-1;
4469
4470 while (text_display_end < text_end && *text_display_end != '\0' &&
4471 (text_display_end[0] != '#' || text_display_end[1] != '#'))
4472 text_display_end++;
4473 return text_display_end;
4474}
4475
4476// Internal ImGui functions to render text
4477// RenderText***() functions calls ImDrawList::AddText() calls ImBitmapFont::RenderText()
4478void ImGui::RenderText(ImVec2 pos, const char *text, const char *text_end, bool hide_text_after_hash)
4479{
4480 ImGuiContext &g = *GImGui;
4481 ImGuiWindow *window = g.CurrentWindow;
4482
4483 // Hide anything after a '##' string
4484 const char *text_display_end;
4485 if (hide_text_after_hash)
4486 {
4487 text_display_end = FindRenderedTextEnd(text, text_end);
4488 }
4489 else
4490 {
4491 if (!text_end)
4492 text_end = text + ImStrlen(text); // FIXME-OPT
4493 text_display_end = text_end;
4494 }
4495
4496 if (text != text_display_end)
4497 {
4498 window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end);
4499 if (g.LogEnabled)
4500 LogRenderedText(&pos, text, text_display_end);
4501 }
4502}
4503
4504void ImGui::RenderTextWrapped(ImVec2 pos, const char *text, const char *text_end, float wrap_width)
4505{
4506 ImGuiContext &g = *GImGui;
4507 ImGuiWindow *window = g.CurrentWindow;
4508
4509 if (!text_end)
4510 text_end = text + ImStrlen(text); // FIXME-OPT
4511
4512 if (text != text_end)
4513 {
4514 window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_end, wrap_width);
4515 if (g.LogEnabled)
4516 LogRenderedText(&pos, text, text_end);
4517 }
4518}
4519
4520// Default clip_rect uses (pos_min,pos_max)
4521// Handle clipping on CPU immediately (vs typically let the GPU clip the triangles that are overlapping the clipping
4522// rectangle edges)
4523// FIXME-OPT: Since we have or calculate text_size we could coarse clip whole block immediately, especially for text
4524// above draw_list->DrawList. Effectively as this is called from widget doing their own coarse clipping it's not very
4525// valuable presently. Next time function will take better advantage of the render function taking size into account for
4526// coarse clipping.
4527void ImGui::RenderTextClippedEx(ImDrawList *draw_list, const ImVec2 &pos_min, const ImVec2 &pos_max, const char *text,
4528 const char *text_display_end, const ImVec2 *text_size_if_known, const ImVec2 &align,
4529 const ImRect *clip_rect)
4530{
4531 // Perform CPU side clipping for single clipped element to avoid using scissor state
4532 ImVec2 pos = pos_min;
4533 const ImVec2 text_size =
4534 text_size_if_known ? *text_size_if_known : CalcTextSize(text, text_display_end, false, 0.0f);
4535
4536 const ImVec2 *clip_min = clip_rect ? &clip_rect->Min : &pos_min;
4537 const ImVec2 *clip_max = clip_rect ? &clip_rect->Max : &pos_max;
4538 bool need_clipping = (pos.x + text_size.x >= clip_max->x) || (pos.y + text_size.y >= clip_max->y);
4539 if (clip_rect) // If we had no explicit clipping rectangle then pos==clip_min
4540 need_clipping |= (pos.x < clip_min->x) || (pos.y < clip_min->y);
4541
4542 // Align whole block. We should defer that to the better rendering function when we'll have support for individual
4543 // line alignment.
4544 if (align.x > 0.0f)
4545 pos.x = ImMax(pos.x, pos.x + (pos_max.x - pos.x - text_size.x) * align.x);
4546 if (align.y > 0.0f)
4547 pos.y = ImMax(pos.y, pos.y + (pos_max.y - pos.y - text_size.y) * align.y);
4548
4549 // Render
4550 if (need_clipping)
4551 {
4552 ImVec4 fine_clip_rect(clip_min->x, clip_min->y, clip_max->x, clip_max->y);
4553 draw_list->AddText(NULL, 0.0f, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, &fine_clip_rect);
4554 }
4555 else
4556 {
4557 draw_list->AddText(NULL, 0.0f, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, NULL);
4558 }
4559}
4560
4561void ImGui::RenderTextClipped(const ImVec2 &pos_min, const ImVec2 &pos_max, const char *text, const char *text_end,
4562 const ImVec2 *text_size_if_known, const ImVec2 &align, const ImRect *clip_rect)
4563{
4564 // Hide anything after a '##' string
4565 const char *text_display_end = FindRenderedTextEnd(text, text_end);
4566 const int text_len = (int)(text_display_end - text);
4567 if (text_len == 0)
4568 return;
4569
4570 ImGuiContext &g = *GImGui;
4571 ImGuiWindow *window = g.CurrentWindow;
4572 RenderTextClippedEx(window->DrawList, pos_min, pos_max, text, text_display_end, text_size_if_known, align,
4573 clip_rect);
4574 if (g.LogEnabled)
4575 LogRenderedText(&pos_min, text, text_display_end);
4576}
4577
4578// Another overly complex function until we reorganize everything into a nice all-in-one helper.
4579// This is made more complex because we have dissociated the layout rectangle (pos_min..pos_max) which define _where_
4580// the ellipsis is, from actual clipping of text and limit of the ellipsis display. This is because in the context of
4581// tabs we selectively hide part of the text when the Close Button appears, but we don't want the ellipsis to move.
4582void ImGui::RenderTextEllipsis(ImDrawList *draw_list, const ImVec2 &pos_min, const ImVec2 &pos_max, float clip_max_x,
4583 float ellipsis_max_x, const char *text, const char *text_end_full,
4584 const ImVec2 *text_size_if_known)
4585{
4586 ImGuiContext &g = *GImGui;
4587 if (text_end_full == NULL)
4588 text_end_full = FindRenderedTextEnd(text);
4589 const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcTextSize(text, text_end_full, false, 0.0f);
4590
4591 // draw_list->AddLine(ImVec2(pos_max.x, pos_min.y - 4), ImVec2(pos_max.x, pos_max.y + 4), IM_COL32(0, 0, 255, 255));
4592 // draw_list->AddLine(ImVec2(ellipsis_max_x, pos_min.y-2), ImVec2(ellipsis_max_x, pos_max.y+2), IM_COL32(0, 255, 0,
4593 // 255)); draw_list->AddLine(ImVec2(clip_max_x, pos_min.y), ImVec2(clip_max_x, pos_max.y), IM_COL32(255, 0, 0,
4594 // 255));
4595 // FIXME: We could technically remove (last_glyph->AdvanceX - last_glyph->X1) from text_size.x here and save a few
4596 // pixels.
4597 if (text_size.x > pos_max.x - pos_min.x)
4598 {
4599 // Hello wo...
4600 // | | |
4601 // min max ellipsis_max
4602 // <-> this is generally some padding value
4603
4604 ImFont *font = draw_list->_Data->Font;
4605 const float font_size = draw_list->_Data->FontSize;
4606 const float font_scale = draw_list->_Data->FontScale;
4607 const char *text_end_ellipsis = NULL;
4608 const float ellipsis_width = font->EllipsisWidth * font_scale;
4609
4610 // We can now claim the space between pos_max.x and ellipsis_max.x
4611 const float text_avail_width = ImMax((ImMax(pos_max.x, ellipsis_max_x) - ellipsis_width) - pos_min.x, 1.0f);
4612 float text_size_clipped_x =
4613 font->CalcTextSizeA(font_size, text_avail_width, 0.0f, text, text_end_full, &text_end_ellipsis).x;
4614 if (text == text_end_ellipsis && text_end_ellipsis < text_end_full)
4615 {
4616 // Always display at least 1 character if there's no room for character + ellipsis
4617 text_end_ellipsis = text + ImTextCountUtf8BytesFromChar(text, text_end_full);
4618 text_size_clipped_x = font->CalcTextSizeA(font_size, FLT_MAX, 0.0f, text, text_end_ellipsis).x;
4619 }
4620 while (text_end_ellipsis > text && ImCharIsBlankA(text_end_ellipsis[-1]))
4621 {
4622 // Trim trailing space before ellipsis (FIXME: Supporting non-ascii blanks would be nice, for this we need a
4623 // function to backtrack in UTF-8 text)
4624 text_end_ellipsis--;
4625 text_size_clipped_x -=
4626 font->CalcTextSizeA(font_size, FLT_MAX, 0.0f, text_end_ellipsis, text_end_ellipsis + 1)
4627 .x; // Ascii blanks are always 1 byte
4628 }
4629
4630 // Render text, render ellipsis
4631 RenderTextClippedEx(draw_list, pos_min, ImVec2(clip_max_x, pos_max.y), text, text_end_ellipsis, &text_size,
4632 ImVec2(0.0f, 0.0f));
4633 ImVec2 ellipsis_pos = ImTrunc(ImVec2(pos_min.x + text_size_clipped_x, pos_min.y));
4634 if (ellipsis_pos.x + ellipsis_width <= ellipsis_max_x)
4635 for (int i = 0; i < font->EllipsisCharCount; i++, ellipsis_pos.x += font->EllipsisCharStep * font_scale)
4636 font->RenderChar(draw_list, font_size, ellipsis_pos, GetColorU32(ImGuiCol_Text), font->EllipsisChar);
4637 }
4638 else
4639 {
4640 RenderTextClippedEx(draw_list, pos_min, ImVec2(clip_max_x, pos_max.y), text, text_end_full, &text_size,
4641 ImVec2(0.0f, 0.0f));
4642 }
4643
4644 if (g.LogEnabled)
4645 LogRenderedText(&pos_min, text, text_end_full);
4646}
4647
4648// Render a rectangle shaped with optional rounding and borders
4649void ImGui::RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool borders, float rounding)
4650{
4651 ImGuiContext &g = *GImGui;
4652 ImGuiWindow *window = g.CurrentWindow;
4653 window->DrawList->AddRectFilled(p_min, p_max, fill_col, rounding);
4654 const float border_size = g.Style.FrameBorderSize;
4655 if (borders && border_size > 0.0f)
4656 {
4657 window->DrawList->AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), GetColorU32(ImGuiCol_BorderShadow),
4658 rounding, 0, border_size);
4659 window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, 0, border_size);
4660 }
4661}
4662
4663void ImGui::RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding)
4664{
4665 ImGuiContext &g = *GImGui;
4666 ImGuiWindow *window = g.CurrentWindow;
4667 const float border_size = g.Style.FrameBorderSize;
4668 if (border_size > 0.0f)
4669 {
4670 window->DrawList->AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), GetColorU32(ImGuiCol_BorderShadow),
4671 rounding, 0, border_size);
4672 window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, 0, border_size);
4673 }
4674}
4675
4676void ImGui::RenderNavCursor(const ImRect &bb, ImGuiID id, ImGuiNavRenderCursorFlags flags)
4677{
4678 ImGuiContext &g = *GImGui;
4679 if (id != g.NavId)
4680 return;
4681 if (!g.NavCursorVisible && !(flags & ImGuiNavRenderCursorFlags_AlwaysDraw))
4682 return;
4683 if (id == g.LastItemData.ID && (g.LastItemData.ItemFlags & ImGuiItemFlags_NoNav))
4684 return;
4685 ImGuiWindow *window = g.CurrentWindow;
4686 if (window->DC.NavHideHighlightOneFrame)
4687 return;
4688
4689 float rounding = (flags & ImGuiNavRenderCursorFlags_NoRounding) ? 0.0f : g.Style.FrameRounding;
4690 ImRect display_rect = bb;
4691 display_rect.ClipWith(window->ClipRect);
4692 const float thickness = 2.0f;
4693 if (flags & ImGuiNavRenderCursorFlags_Compact)
4694 {
4695 window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavCursor), rounding, 0,
4696 thickness);
4697 }
4698 else
4699 {
4700 const float distance = 3.0f + thickness * 0.5f;
4701 display_rect.Expand(ImVec2(distance, distance));
4702 bool fully_visible = window->ClipRect.Contains(display_rect);
4703 if (!fully_visible)
4704 window->DrawList->PushClipRect(display_rect.Min, display_rect.Max);
4705 window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavCursor), rounding, 0,
4706 thickness);
4707 if (!fully_visible)
4708 window->DrawList->PopClipRect();
4709 }
4710}
4711
4712void ImGui::RenderMouseCursor(ImVec2 base_pos, float base_scale, ImGuiMouseCursor mouse_cursor, ImU32 col_fill,
4713 ImU32 col_border, ImU32 col_shadow)
4714{
4715 ImGuiContext &g = *GImGui;
4716 if (mouse_cursor <= ImGuiMouseCursor_None ||
4717 mouse_cursor >= ImGuiMouseCursor_COUNT) // We intentionally accept out of bound values.
4718 mouse_cursor = ImGuiMouseCursor_Arrow;
4719 ImFontAtlas *font_atlas = g.DrawListSharedData.Font->ContainerAtlas;
4720 for (ImGuiViewportP *viewport : g.Viewports)
4721 {
4722 // We scale cursor with current viewport/monitor, however Windows 10 for its own hardware cursor seems to be
4723 // using a different scale factor.
4724 ImVec2 offset, size, uv[4];
4725 if (!ImFontAtlasGetMouseCursorTexData(font_atlas, mouse_cursor, &offset, &size, &uv[0], &uv[2]))
4726 continue;
4727 const ImVec2 pos = base_pos - offset;
4728 const float scale = base_scale * viewport->DpiScale;
4729 if (!viewport->GetMainRect().Overlaps(ImRect(pos, pos + ImVec2(size.x + 2, size.y + 2) * scale)))
4730 continue;
4731 ImDrawList *draw_list = GetForegroundDrawList(viewport);
4732 ImTextureID tex_id = font_atlas->TexID;
4733 draw_list->PushTextureID(tex_id);
4734 draw_list->AddImage(tex_id, pos + ImVec2(1, 0) * scale, pos + (ImVec2(1, 0) + size) * scale, uv[2], uv[3],
4735 col_shadow);
4736 draw_list->AddImage(tex_id, pos + ImVec2(2, 0) * scale, pos + (ImVec2(2, 0) + size) * scale, uv[2], uv[3],
4737 col_shadow);
4738 draw_list->AddImage(tex_id, pos, pos + size * scale, uv[2], uv[3], col_border);
4739 draw_list->AddImage(tex_id, pos, pos + size * scale, uv[0], uv[1], col_fill);
4740 if (mouse_cursor == ImGuiMouseCursor_Wait || mouse_cursor == ImGuiMouseCursor_Progress)
4741 {
4742 float a_min = ImFmod((float)g.Time * 5.0f, 2.0f * IM_PI);
4743 float a_max = a_min + IM_PI * 1.65f;
4744 draw_list->PathArcTo(pos + ImVec2(14, -1) * scale, 6.0f * scale, a_min, a_max);
4745 draw_list->PathStroke(col_fill, ImDrawFlags_None, 3.0f * scale);
4746 }
4747 draw_list->PopTextureID();
4748 }
4749}
4750
4751//-----------------------------------------------------------------------------
4752// [SECTION] INITIALIZATION, SHUTDOWN
4753//-----------------------------------------------------------------------------
4754
4755// Internal state access - if you want to share Dear ImGui state between modules (e.g. DLL) or allocate it yourself
4756// Note that we still point to some static data and members (such as GFontAtlas), so the state instance you end up using
4757// will point to the static data within its module
4758ImGuiContext *ImGui::GetCurrentContext()
4759{
4760 return GImGui;
4761}
4762
4763void ImGui::SetCurrentContext(ImGuiContext *ctx)
4764{
4765#ifdef IMGUI_SET_CURRENT_CONTEXT_FUNC
4766 IMGUI_SET_CURRENT_CONTEXT_FUNC(ctx); // For custom thread-based hackery you may want to have control over this.
4767#else
4768 GImGui = ctx;
4769#endif
4770}
4771
4772void ImGui::SetAllocatorFunctions(ImGuiMemAllocFunc alloc_func, ImGuiMemFreeFunc free_func, void *user_data)
4773{
4774 GImAllocatorAllocFunc = alloc_func;
4775 GImAllocatorFreeFunc = free_func;
4776 GImAllocatorUserData = user_data;
4777}
4778
4779// This is provided to facilitate copying allocators from one static/DLL boundary to another (e.g. retrieve default
4780// allocator of your executable address space)
4781void ImGui::GetAllocatorFunctions(ImGuiMemAllocFunc *p_alloc_func, ImGuiMemFreeFunc *p_free_func, void **p_user_data)
4782{
4783 *p_alloc_func = GImAllocatorAllocFunc;
4784 *p_free_func = GImAllocatorFreeFunc;
4785 *p_user_data = GImAllocatorUserData;
4786}
4787
4788ImGuiContext *ImGui::CreateContext(ImFontAtlas *shared_font_atlas)
4789{
4790 ImGuiContext *prev_ctx = GetCurrentContext();
4791 ImGuiContext *ctx = IM_NEW(ImGuiContext)(shared_font_atlas);
4792 SetCurrentContext(ctx);
4793 Initialize();
4794 if (prev_ctx != NULL)
4795 SetCurrentContext(prev_ctx); // Restore previous context if any, else keep new one.
4796 return ctx;
4797}
4798
4799void ImGui::DestroyContext(ImGuiContext *ctx)
4800{
4801 ImGuiContext *prev_ctx = GetCurrentContext();
4802 if (ctx == NULL) //-V1051
4803 ctx = prev_ctx;
4804 SetCurrentContext(ctx);
4805 Shutdown();
4806 SetCurrentContext((prev_ctx != ctx) ? prev_ctx : NULL);
4807 IM_DELETE(ctx);
4808}
4809
4810// IMPORTANT: interactive elements requires a fixed ###xxx suffix, it must be same in ALL languages to allow for
4811// automation.
4812static const ImGuiLocEntry GLocalizationEntriesEnUS[] = {
4813 {ImGuiLocKey_VersionStr, "Dear ImGui " IMGUI_VERSION " (" IM_STRINGIFY(IMGUI_VERSION_NUM) ")"},
4814 {ImGuiLocKey_TableSizeOne, "Size column to fit###SizeOne"},
4815 {ImGuiLocKey_TableSizeAllFit, "Size all columns to fit###SizeAll"},
4816 {ImGuiLocKey_TableSizeAllDefault, "Size all columns to default###SizeAll"},
4817 {ImGuiLocKey_TableResetOrder, "Reset order###ResetOrder"},
4818 {ImGuiLocKey_WindowingMainMenuBar, "(Main menu bar)"},
4819 {ImGuiLocKey_WindowingPopup, "(Popup)"},
4820 {ImGuiLocKey_WindowingUntitled, "(Untitled)"},
4821 {ImGuiLocKey_OpenLink_s, "Open '%s'"},
4822 {ImGuiLocKey_CopyLink, "Copy Link###CopyLink"},
4823 {ImGuiLocKey_DockingHideTabBar, "Hide tab bar###HideTabBar"},
4824 {ImGuiLocKey_DockingHoldShiftToDock, "Hold SHIFT to enable Docking window."},
4825 {ImGuiLocKey_DockingDragToUndockOrMoveNode, "Click and drag to move or undock whole node."},
4826};
4827
4828ImGuiContext::ImGuiContext(ImFontAtlas *shared_font_atlas)
4829{
4830 IO.Ctx = this;
4831 InputTextState.Ctx = this;
4832
4833 Initialized = false;
4834 ConfigFlagsCurrFrame = ConfigFlagsLastFrame = ImGuiConfigFlags_None;
4835 FontAtlasOwnedByContext = shared_font_atlas ? false : true;
4836 Font = NULL;
4837 FontSize = FontBaseSize = FontScale = CurrentDpiScale = 0.0f;
4838 IO.Fonts = shared_font_atlas ? shared_font_atlas : IM_NEW(ImFontAtlas)();
4839 Time = 0.0f;
4840 FrameCount = 0;
4841 FrameCountEnded = FrameCountPlatformEnded = FrameCountRendered = -1;
4842 WithinEndChildID = 0;
4843 WithinFrameScope = WithinFrameScopeWithImplicitWindow = false;
4844 GcCompactAll = false;
4845 TestEngineHookItems = false;
4846 TestEngine = NULL;
4847 memset(ContextName, 0, sizeof(ContextName));
4848
4849 InputEventsNextMouseSource = ImGuiMouseSource_Mouse;
4850 InputEventsNextEventId = 1;
4851
4852 WindowsActiveCount = 0;
4853 WindowsBorderHoverPadding = 0.0f;
4854 CurrentWindow = NULL;
4855 HoveredWindow = NULL;
4856 HoveredWindowUnderMovingWindow = NULL;
4857 HoveredWindowBeforeClear = NULL;
4858 MovingWindow = NULL;
4859 WheelingWindow = NULL;
4860 WheelingWindowStartFrame = WheelingWindowScrolledFrame = -1;
4861 WheelingWindowReleaseTimer = 0.0f;
4862
4863 DebugDrawIdConflicts = 0;
4864 DebugHookIdInfo = 0;
4865 HoveredId = HoveredIdPreviousFrame = 0;
4866 HoveredIdPreviousFrameItemCount = 0;
4867 HoveredIdAllowOverlap = false;
4868 HoveredIdIsDisabled = false;
4869 HoveredIdTimer = HoveredIdNotActiveTimer = 0.0f;
4870 ItemUnclipByLog = false;
4871 ActiveId = 0;
4872 ActiveIdIsAlive = 0;
4873 ActiveIdTimer = 0.0f;
4874 ActiveIdIsJustActivated = false;
4875 ActiveIdAllowOverlap = false;
4876 ActiveIdNoClearOnFocusLoss = false;
4877 ActiveIdHasBeenPressedBefore = false;
4878 ActiveIdHasBeenEditedBefore = false;
4879 ActiveIdHasBeenEditedThisFrame = false;
4880 ActiveIdFromShortcut = false;
4881 ActiveIdClickOffset = ImVec2(-1, -1);
4882 ActiveIdWindow = NULL;
4883 ActiveIdSource = ImGuiInputSource_None;
4884 ActiveIdMouseButton = -1;
4885 ActiveIdPreviousFrame = 0;
4886 memset(&DeactivatedItemData, 0, sizeof(DeactivatedItemData));
4887 memset(&ActiveIdValueOnActivation, 0, sizeof(ActiveIdValueOnActivation));
4888 LastActiveId = 0;
4889 LastActiveIdTimer = 0.0f;
4890
4891 LastKeyboardKeyPressTime = LastKeyModsChangeTime = LastKeyModsChangeFromNoneTime = -1.0;
4892
4893 ActiveIdUsingNavDirMask = 0x00;
4894 ActiveIdUsingAllKeyboardKeys = false;
4895
4896 CurrentFocusScopeId = 0;
4897 CurrentItemFlags = ImGuiItemFlags_None;
4898 DebugShowGroupRects = false;
4899
4900 CurrentViewport = NULL;
4901 MouseViewport = MouseLastHoveredViewport = NULL;
4902 PlatformLastFocusedViewportId = 0;
4903 ViewportCreatedCount = PlatformWindowsCreatedCount = 0;
4904 ViewportFocusedStampCount = 0;
4905
4906 NavCursorVisible = false;
4907 NavHighlightItemUnderNav = false;
4908 NavMousePosDirty = false;
4909 NavIdIsAlive = false;
4910 NavId = 0;
4911 NavWindow = NULL;
4912 NavFocusScopeId = NavActivateId = NavActivateDownId = NavActivatePressedId = 0;
4913 NavLayer = ImGuiNavLayer_Main;
4914 NavNextActivateId = 0;
4915 NavActivateFlags = NavNextActivateFlags = ImGuiActivateFlags_None;
4916 NavHighlightActivatedId = 0;
4917 NavHighlightActivatedTimer = 0.0f;
4918 NavInputSource = ImGuiInputSource_Keyboard;
4919 NavLastValidSelectionUserData = ImGuiSelectionUserData_Invalid;
4920 NavCursorHideFrames = 0;
4921
4922 NavAnyRequest = false;
4923 NavInitRequest = false;
4924 NavInitRequestFromMove = false;
4925 NavMoveSubmitted = false;
4926 NavMoveScoringItems = false;
4927 NavMoveForwardToNextFrame = false;
4928 NavMoveFlags = ImGuiNavMoveFlags_None;
4929 NavMoveScrollFlags = ImGuiScrollFlags_None;
4930 NavMoveKeyMods = ImGuiMod_None;
4931 NavMoveDir = NavMoveDirForDebug = NavMoveClipDir = ImGuiDir_None;
4932 NavScoringDebugCount = 0;
4933 NavTabbingDir = 0;
4934 NavTabbingCounter = 0;
4935
4936 NavJustMovedFromFocusScopeId = NavJustMovedToId = NavJustMovedToFocusScopeId = 0;
4937 NavJustMovedToKeyMods = ImGuiMod_None;
4938 NavJustMovedToIsTabbing = false;
4939 NavJustMovedToHasSelectionData = false;
4940
4941 // All platforms use Ctrl+Tab but Ctrl<>Super are swapped on Mac...
4942 // FIXME: Because this value is stored, it annoyingly interfere with toggling io.ConfigMacOSXBehaviors updating
4943 // this..
4944 ConfigNavWindowingWithGamepad = true;
4945 ConfigNavWindowingKeyNext =
4946 IO.ConfigMacOSXBehaviors ? (ImGuiMod_Super | ImGuiKey_Tab) : (ImGuiMod_Ctrl | ImGuiKey_Tab);
4947 ConfigNavWindowingKeyPrev = IO.ConfigMacOSXBehaviors ? (ImGuiMod_Super | ImGuiMod_Shift | ImGuiKey_Tab)
4948 : (ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Tab);
4949 NavWindowingTarget = NavWindowingTargetAnim = NavWindowingListWindow = NULL;
4950 NavWindowingInputSource = ImGuiInputSource_None;
4951 NavWindowingTimer = NavWindowingHighlightAlpha = 0.0f;
4952 NavWindowingToggleLayer = false;
4953 NavWindowingToggleKey = ImGuiKey_None;
4954
4955 DimBgRatio = 0.0f;
4956
4957 DragDropActive = DragDropWithinSource = DragDropWithinTarget = false;
4958 DragDropSourceFlags = ImGuiDragDropFlags_None;
4959 DragDropSourceFrameCount = -1;
4960 DragDropMouseButton = -1;
4961 DragDropTargetId = 0;
4962 DragDropAcceptFlags = ImGuiDragDropFlags_None;
4963 DragDropAcceptIdCurrRectSurface = 0.0f;
4964 DragDropAcceptIdPrev = DragDropAcceptIdCurr = 0;
4965 DragDropAcceptFrameCount = -1;
4966 DragDropHoldJustPressedId = 0;
4967 memset(DragDropPayloadBufLocal, 0, sizeof(DragDropPayloadBufLocal));
4968
4969 ClipperTempDataStacked = 0;
4970
4971 CurrentTable = NULL;
4972 TablesTempDataStacked = 0;
4973 CurrentTabBar = NULL;
4974 CurrentMultiSelect = NULL;
4975 MultiSelectTempDataStacked = 0;
4976
4977 HoverItemDelayId = HoverItemDelayIdPreviousFrame = HoverItemUnlockedStationaryId = HoverWindowUnlockedStationaryId =
4978 0;
4979 HoverItemDelayTimer = HoverItemDelayClearTimer = 0.0f;
4980
4981 MouseCursor = ImGuiMouseCursor_Arrow;
4982 MouseStationaryTimer = 0.0f;
4983
4984 TempInputId = 0;
4985 memset(&DataTypeZeroValue, 0, sizeof(DataTypeZeroValue));
4986 BeginMenuDepth = BeginComboDepth = 0;
4987 ColorEditOptions = ImGuiColorEditFlags_DefaultOptions_;
4988 ColorEditCurrentID = ColorEditSavedID = 0;
4989 ColorEditSavedHue = ColorEditSavedSat = 0.0f;
4990 ColorEditSavedColor = 0;
4991 WindowResizeRelativeMode = false;
4992 ScrollbarSeekMode = 0;
4993 ScrollbarClickDeltaToGrabCenter = 0.0f;
4994 SliderGrabClickOffset = 0.0f;
4995 SliderCurrentAccum = 0.0f;
4996 SliderCurrentAccumDirty = false;
4997 DragCurrentAccumDirty = false;
4998 DragCurrentAccum = 0.0f;
4999 DragSpeedDefaultRatio = 1.0f / 100.0f;
5000 DisabledAlphaBackup = 0.0f;
5001 DisabledStackSize = 0;
5002 TooltipOverrideCount = 0;
5003 TooltipPreviousWindow = NULL;
5004
5005 PlatformImeData.InputPos = ImVec2(0.0f, 0.0f);
5006 PlatformImeDataPrev.InputPos = ImVec2(-1.0f, -1.0f); // Different to ensure initial submission
5007 PlatformImeViewport = 0;
5008
5009 DockNodeWindowMenuHandler = NULL;
5010
5011 SettingsLoaded = false;
5012 SettingsDirtyTimer = 0.0f;
5013 HookIdNext = 0;
5014
5015 memset(LocalizationTable, 0, sizeof(LocalizationTable));
5016
5017 LogEnabled = false;
5018 LogFlags = ImGuiLogFlags_None;
5019 LogWindow = NULL;
5020 LogNextPrefix = LogNextSuffix = NULL;
5021 LogFile = NULL;
5022 LogLinePosY = FLT_MAX;
5023 LogLineFirstItem = false;
5024 LogDepthRef = 0;
5025 LogDepthToExpand = LogDepthToExpandDefault = 2;
5026
5027 ErrorCallback = NULL;
5028 ErrorCallbackUserData = NULL;
5029 ErrorFirst = true;
5030 ErrorCountCurrentFrame = 0;
5031 StackSizesInBeginForCurrentWindow = NULL;
5032
5033 DebugDrawIdConflictsCount = 0;
5034 DebugLogFlags = ImGuiDebugLogFlags_EventError | ImGuiDebugLogFlags_OutputToTTY;
5035 DebugLocateId = 0;
5036 DebugLogSkippedErrors = 0;
5037 DebugLogAutoDisableFlags = ImGuiDebugLogFlags_None;
5038 DebugLogAutoDisableFrames = 0;
5039 DebugLocateFrames = 0;
5040 DebugBeginReturnValueCullDepth = -1;
5041 DebugItemPickerActive = false;
5042 DebugItemPickerMouseButton = ImGuiMouseButton_Left;
5043 DebugItemPickerBreakId = 0;
5044 DebugFlashStyleColorTime = 0.0f;
5045 DebugFlashStyleColorIdx = ImGuiCol_COUNT;
5046 DebugHoveredDockNode = NULL;
5047
5048 // Same as DebugBreakClearData(). Those fields are scattered in their respective subsystem to stay in hot-data
5049 // locations
5050 DebugBreakInWindow = 0;
5051 DebugBreakInTable = 0;
5052 DebugBreakInLocateId = false;
5053 DebugBreakKeyChord = ImGuiKey_Pause;
5054 DebugBreakInShortcutRouting = ImGuiKey_None;
5055
5056 memset(FramerateSecPerFrame, 0, sizeof(FramerateSecPerFrame));
5057 FramerateSecPerFrameIdx = FramerateSecPerFrameCount = 0;
5058 FramerateSecPerFrameAccum = 0.0f;
5059 WantCaptureMouseNextFrame = WantCaptureKeyboardNextFrame = WantTextInputNextFrame = -1;
5060 memset(TempKeychordName, 0, sizeof(TempKeychordName));
5061}
5062
5063void ImGui::Initialize()
5064{
5065 ImGuiContext &g = *GImGui;
5066 IM_ASSERT(!g.Initialized && !g.SettingsLoaded);
5067
5068 // Add .ini handle for ImGuiWindow and ImGuiTable types
5069 {
5070 ImGuiSettingsHandler ini_handler;
5071 ini_handler.TypeName = "Window";
5072 ini_handler.TypeHash = ImHashStr("Window");
5073 ini_handler.ClearAllFn = WindowSettingsHandler_ClearAll;
5074 ini_handler.ReadOpenFn = WindowSettingsHandler_ReadOpen;
5075 ini_handler.ReadLineFn = WindowSettingsHandler_ReadLine;
5076 ini_handler.ApplyAllFn = WindowSettingsHandler_ApplyAll;
5077 ini_handler.WriteAllFn = WindowSettingsHandler_WriteAll;
5078 AddSettingsHandler(&ini_handler);
5079 }
5080 TableSettingsAddSettingsHandler();
5081
5082 // Setup default localization table
5083 LocalizeRegisterEntries(GLocalizationEntriesEnUS, IM_ARRAYSIZE(GLocalizationEntriesEnUS));
5084
5085 // Setup default ImGuiPlatformIO clipboard/IME handlers.
5086 g.PlatformIO.Platform_GetClipboardTextFn =
5087 Platform_GetClipboardTextFn_DefaultImpl; // Platform dependent default implementations
5088 g.PlatformIO.Platform_SetClipboardTextFn = Platform_SetClipboardTextFn_DefaultImpl;
5089 g.PlatformIO.Platform_OpenInShellFn = Platform_OpenInShellFn_DefaultImpl;
5090 g.PlatformIO.Platform_SetImeDataFn = Platform_SetImeDataFn_DefaultImpl;
5091
5092 // Create default viewport
5093 ImGuiViewportP *viewport = IM_NEW(ImGuiViewportP)();
5094 viewport->ID = IMGUI_VIEWPORT_DEFAULT_ID;
5095 viewport->Idx = 0;
5096 viewport->PlatformWindowCreated = true;
5097 viewport->Flags = ImGuiViewportFlags_OwnedByApp;
5098 g.Viewports.push_back(viewport);
5099 g.TempBuffer.resize(1024 * 3 + 1, 0);
5100 g.ViewportCreatedCount++;
5101 g.PlatformIO.Viewports.push_back(g.Viewports[0]);
5102
5103 // Build KeysMayBeCharInput[] lookup table (1 bool per named key)
5104 for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1))
5105 if ((key >= ImGuiKey_0 && key <= ImGuiKey_9) || (key >= ImGuiKey_A && key <= ImGuiKey_Z) ||
5106 (key >= ImGuiKey_Keypad0 && key <= ImGuiKey_Keypad9) || key == ImGuiKey_Tab || key == ImGuiKey_Space ||
5107 key == ImGuiKey_Apostrophe || key == ImGuiKey_Comma || key == ImGuiKey_Minus || key == ImGuiKey_Period ||
5108 key == ImGuiKey_Slash || key == ImGuiKey_Semicolon || key == ImGuiKey_Equal ||
5109 key == ImGuiKey_LeftBracket || key == ImGuiKey_RightBracket || key == ImGuiKey_GraveAccent ||
5110 key == ImGuiKey_KeypadDecimal || key == ImGuiKey_KeypadDivide || key == ImGuiKey_KeypadMultiply ||
5111 key == ImGuiKey_KeypadSubtract || key == ImGuiKey_KeypadAdd || key == ImGuiKey_KeypadEqual)
5112 g.KeysMayBeCharInput.SetBit(key);
5113
5114#ifdef IMGUI_HAS_DOCK
5115 // Initialize Docking
5116 DockContextInitialize(&g);
5117#endif
5118
5119 g.Initialized = true;
5120}
5121
5122// This function is merely here to free heap allocations.
5123void ImGui::Shutdown()
5124{
5125 ImGuiContext &g = *GImGui;
5126 IM_ASSERT_USER_ERROR(g.IO.BackendPlatformUserData == NULL, "Forgot to shutdown Platform backend?");
5127 IM_ASSERT_USER_ERROR(g.IO.BackendRendererUserData == NULL, "Forgot to shutdown Renderer backend?");
5128
5129 // The fonts atlas can be used prior to calling NewFrame(), so we clear it even if g.Initialized is FALSE (which
5130 // would happen if we never called NewFrame)
5131 if (g.IO.Fonts && g.FontAtlasOwnedByContext)
5132 {
5133 g.IO.Fonts->Locked = false;
5134 IM_DELETE(g.IO.Fonts);
5135 }
5136 g.IO.Fonts = NULL;
5137 g.DrawListSharedData.TempBuffer.clear();
5138
5139 // Cleanup of other data are conditional on actually having initialized Dear ImGui.
5140 if (!g.Initialized)
5141 return;
5142
5143 // Save settings (unless we haven't attempted to load them: CreateContext/DestroyContext without a call to NewFrame
5144 // shouldn't save an empty file)
5145 if (g.SettingsLoaded && g.IO.IniFilename != NULL)
5146 SaveIniSettingsToDisk(g.IO.IniFilename);
5147
5148 // Destroy platform windows
5149 DestroyPlatformWindows();
5150
5151 // Shutdown extensions
5152 DockContextShutdown(&g);
5153
5154 CallContextHooks(&g, ImGuiContextHookType_Shutdown);
5155
5156 // Clear everything else
5157 g.Windows.clear_delete();
5158 g.WindowsFocusOrder.clear();
5159 g.WindowsTempSortBuffer.clear();
5160 g.CurrentWindow = NULL;
5161 g.CurrentWindowStack.clear();
5162 g.WindowsById.Clear();
5163 g.NavWindow = NULL;
5164 g.HoveredWindow = g.HoveredWindowUnderMovingWindow = NULL;
5165 g.ActiveIdWindow = NULL;
5166 g.MovingWindow = NULL;
5167
5168 g.KeysRoutingTable.Clear();
5169
5170 g.ColorStack.clear();
5171 g.StyleVarStack.clear();
5172 g.FontStack.clear();
5173 g.OpenPopupStack.clear();
5174 g.BeginPopupStack.clear();
5175 g.TreeNodeStack.clear();
5176
5177 g.CurrentViewport = g.MouseViewport = g.MouseLastHoveredViewport = NULL;
5178 g.Viewports.clear_delete();
5179
5180 g.TabBars.Clear();
5181 g.CurrentTabBarStack.clear();
5182 g.ShrinkWidthBuffer.clear();
5183
5184 g.ClipperTempData.clear_destruct();
5185
5186 g.Tables.Clear();
5187 g.TablesTempData.clear_destruct();
5188 g.DrawChannelsTempMergeBuffer.clear();
5189
5190 g.MultiSelectStorage.Clear();
5191 g.MultiSelectTempData.clear_destruct();
5192
5193 g.ClipboardHandlerData.clear();
5194 g.MenusIdSubmittedThisFrame.clear();
5195 g.InputTextState.ClearFreeMemory();
5196 g.InputTextDeactivatedState.ClearFreeMemory();
5197
5198 g.SettingsWindows.clear();
5199 g.SettingsHandlers.clear();
5200
5201 if (g.LogFile)
5202 {
5203#ifndef IMGUI_DISABLE_TTY_FUNCTIONS
5204 if (g.LogFile != stdout)
5205#endif
5206 ImFileClose(g.LogFile);
5207 g.LogFile = NULL;
5208 }
5209 g.LogBuffer.clear();
5210 g.DebugLogBuf.clear();
5211 g.DebugLogIndex.clear();
5212
5213 g.Initialized = false;
5214}
5215
5216// No specific ordering/dependency support, will see as needed
5217ImGuiID ImGui::AddContextHook(ImGuiContext *ctx, const ImGuiContextHook *hook)
5218{
5219 ImGuiContext &g = *ctx;
5220 IM_ASSERT(hook->Callback != NULL && hook->HookId == 0 && hook->Type != ImGuiContextHookType_PendingRemoval_);
5221 g.Hooks.push_back(*hook);
5222 g.Hooks.back().HookId = ++g.HookIdNext;
5223 return g.HookIdNext;
5224}
5225
5226// Deferred removal, avoiding issue with changing vector while iterating it
5227void ImGui::RemoveContextHook(ImGuiContext *ctx, ImGuiID hook_id)
5228{
5229 ImGuiContext &g = *ctx;
5230 IM_ASSERT(hook_id != 0);
5231 for (ImGuiContextHook &hook : g.Hooks)
5232 if (hook.HookId == hook_id)
5233 hook.Type = ImGuiContextHookType_PendingRemoval_;
5234}
5235
5236// Call context hooks (used by e.g. test engine)
5237// We assume a small number of hooks so all stored in same array
5238void ImGui::CallContextHooks(ImGuiContext *ctx, ImGuiContextHookType hook_type)
5239{
5240 ImGuiContext &g = *ctx;
5241 for (ImGuiContextHook &hook : g.Hooks)
5242 if (hook.Type == hook_type)
5243 hook.Callback(&g, &hook);
5244}
5245
5246//-----------------------------------------------------------------------------
5247// [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!)
5248//-----------------------------------------------------------------------------
5249
5250// ImGuiWindow is mostly a dumb struct. It merely has a constructor and a few helper methods
5251ImGuiWindow::ImGuiWindow(ImGuiContext *ctx, const char *name) : DrawListInst(NULL)
5252{
5253 memset(this, 0, sizeof(*this));
5254 Ctx = ctx;
5255 Name = ImStrdup(name);
5256 NameBufLen = (int)ImStrlen(name) + 1;
5257 ID = ImHashStr(name);
5258 IDStack.push_back(ID);
5259 ViewportAllowPlatformMonitorExtend = -1;
5260 ViewportPos = ImVec2(FLT_MAX, FLT_MAX);
5261 MoveId = GetID("#MOVE");
5262 TabId = GetID("#TAB");
5263 ScrollTarget = ImVec2(FLT_MAX, FLT_MAX);
5264 ScrollTargetCenterRatio = ImVec2(0.5f, 0.5f);
5265 AutoFitFramesX = AutoFitFramesY = -1;
5266 AutoPosLastDirection = ImGuiDir_None;
5267 SetWindowPosAllowFlags = SetWindowSizeAllowFlags = SetWindowCollapsedAllowFlags = SetWindowDockAllowFlags = 0;
5268 SetWindowPosVal = SetWindowPosPivot = ImVec2(FLT_MAX, FLT_MAX);
5269 LastFrameActive = -1;
5270 LastFrameJustFocused = -1;
5271 LastTimeActive = -1.0f;
5272 FontRefSize = 0.0f;
5273 FontWindowScale = FontWindowScaleParents = FontDpiScale = 1.0f;
5274 SettingsOffset = -1;
5275 DockOrder = -1;
5276 DrawList = &DrawListInst;
5277 DrawList->_OwnerName = Name;
5278 DrawList->_Data = &Ctx->DrawListSharedData;
5279 NavPreferredScoringPosRel[0] = NavPreferredScoringPosRel[1] = ImVec2(FLT_MAX, FLT_MAX);
5280 IM_PLACEMENT_NEW(&WindowClass) ImGuiWindowClass();
5281}
5282
5283ImGuiWindow::~ImGuiWindow()
5284{
5285 IM_ASSERT(DrawList == &DrawListInst);
5286 IM_DELETE(Name);
5287 ColumnsStorage.clear_destruct();
5288}
5289
5290static void SetCurrentWindow(ImGuiWindow *window)
5291{
5292 ImGuiContext &g = *GImGui;
5293 g.CurrentWindow = window;
5294 g.StackSizesInBeginForCurrentWindow = g.CurrentWindow ? &g.CurrentWindowStack.back().StackSizesInBegin : NULL;
5295 g.CurrentTable =
5296 window && window->DC.CurrentTableIdx != -1 ? g.Tables.GetByIndex(window->DC.CurrentTableIdx) : NULL;
5297 if (window)
5298 {
5299 g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize();
5300 g.FontScale = g.DrawListSharedData.FontScale = g.FontSize / g.Font->FontSize;
5301 ImGui::NavUpdateCurrentWindowIsScrollPushableX();
5302 }
5303}
5304
5305void ImGui::GcCompactTransientMiscBuffers()
5306{
5307 ImGuiContext &g = *GImGui;
5308 g.ItemFlagsStack.clear();
5309 g.GroupStack.clear();
5310 g.MultiSelectTempDataStacked = 0;
5311 g.MultiSelectTempData.clear_destruct();
5312 TableGcCompactSettings();
5313}
5314
5315// Free up/compact internal window buffers, we can use this when a window becomes unused.
5316// Not freed:
5317// - ImGuiWindow, ImGuiWindowSettings, Name, StateStorage, ColumnsStorage (may hold useful data)
5318// This should have no noticeable visual effect. When the window reappear however, expect new allocation/buffer
5319// growth/copy cost.
5320void ImGui::GcCompactTransientWindowBuffers(ImGuiWindow *window)
5321{
5322 window->MemoryCompacted = true;
5323 window->MemoryDrawListIdxCapacity = window->DrawList->IdxBuffer.Capacity;
5324 window->MemoryDrawListVtxCapacity = window->DrawList->VtxBuffer.Capacity;
5325 window->IDStack.clear();
5326 window->DrawList->_ClearFreeMemory();
5327 window->DC.ChildWindows.clear();
5328 window->DC.ItemWidthStack.clear();
5329 window->DC.TextWrapPosStack.clear();
5330}
5331
5332void ImGui::GcAwakeTransientWindowBuffers(ImGuiWindow *window)
5333{
5334 // We stored capacity of the ImDrawList buffer to reduce growth-caused allocation/copy when awakening.
5335 // The other buffers tends to amortize much faster.
5336 window->MemoryCompacted = false;
5337 window->DrawList->IdxBuffer.reserve(window->MemoryDrawListIdxCapacity);
5338 window->DrawList->VtxBuffer.reserve(window->MemoryDrawListVtxCapacity);
5339 window->MemoryDrawListIdxCapacity = window->MemoryDrawListVtxCapacity = 0;
5340}
5341
5342void ImGui::SetActiveID(ImGuiID id, ImGuiWindow *window)
5343{
5344 ImGuiContext &g = *GImGui;
5345
5346 // Clear previous active id
5347 if (g.ActiveId != 0)
5348 {
5349 // While most behaved code would make an effort to not steal active id during window move/drag operations,
5350 // we at least need to be resilient to it. Canceling the move is rather aggressive and users of 'master' branch
5351 // may prefer the weird ill-defined half working situation ('docking' did assert), so may need to rework that.
5352 if (g.MovingWindow != NULL && g.ActiveId == g.MovingWindow->MoveId)
5353 {
5354 IMGUI_DEBUG_LOG_ACTIVEID("SetActiveID() cancel MovingWindow\n");
5355 g.MovingWindow = NULL;
5356 }
5357
5358 // Store deactivate data
5359 ImGuiDeactivatedItemData *deactivated_data = &g.DeactivatedItemData;
5360 deactivated_data->ID = g.ActiveId;
5361 deactivated_data->ElapseFrame =
5362 (g.LastItemData.ID == g.ActiveId) ? g.FrameCount : g.FrameCount + 1; // FIXME: OK to use LastItemData?
5363 deactivated_data->HasBeenEditedBefore = g.ActiveIdHasBeenEditedBefore;
5364 deactivated_data->IsAlive = (g.ActiveIdIsAlive == g.ActiveId);
5365
5366 // This could be written in a more general way (e.g associate a hook to ActiveId),
5367 // but since this is currently quite an exception we'll leave it as is.
5368 // One common scenario leading to this is: pressing Key ->NavMoveRequestApplyResult() -> ClearActiveID()
5369 if (g.InputTextState.ID == g.ActiveId)
5370 InputTextDeactivateHook(g.ActiveId);
5371 }
5372
5373 // Set active id
5374 g.ActiveIdIsJustActivated = (g.ActiveId != id);
5375 if (g.ActiveIdIsJustActivated)
5376 {
5377 IMGUI_DEBUG_LOG_ACTIVEID("SetActiveID() old:0x%08X (window \"%s\") -> new:0x%08X (window \"%s\")\n", g.ActiveId,
5378 g.ActiveIdWindow ? g.ActiveIdWindow->Name : "", id, window ? window->Name : "");
5379 g.ActiveIdTimer = 0.0f;
5380 g.ActiveIdHasBeenPressedBefore = false;
5381 g.ActiveIdHasBeenEditedBefore = false;
5382 g.ActiveIdMouseButton = -1;
5383 if (id != 0)
5384 {
5385 g.LastActiveId = id;
5386 g.LastActiveIdTimer = 0.0f;
5387 }
5388 }
5389 g.ActiveId = id;
5390 g.ActiveIdAllowOverlap = false;
5391 g.ActiveIdNoClearOnFocusLoss = false;
5392 g.ActiveIdWindow = window;
5393 g.ActiveIdHasBeenEditedThisFrame = false;
5394 g.ActiveIdFromShortcut = false;
5395 if (id)
5396 {
5397 g.ActiveIdIsAlive = id;
5398 g.ActiveIdSource =
5399 (g.NavActivateId == id || g.NavJustMovedToId == id) ? g.NavInputSource : ImGuiInputSource_Mouse;
5400 IM_ASSERT(g.ActiveIdSource != ImGuiInputSource_None);
5401 }
5402
5403 // Clear declaration of inputs claimed by the widget
5404 // (Please note that this is WIP and not all keys/inputs are thoroughly declared by all widgets yet)
5405 g.ActiveIdUsingNavDirMask = 0x00;
5406 g.ActiveIdUsingAllKeyboardKeys = false;
5407}
5408
5409void ImGui::ClearActiveID()
5410{
5411 SetActiveID(0, NULL); // g.ActiveId = 0;
5412}
5413
5414void ImGui::SetHoveredID(ImGuiID id)
5415{
5416 ImGuiContext &g = *GImGui;
5417 g.HoveredId = id;
5418 g.HoveredIdAllowOverlap = false;
5419 if (id != 0 && g.HoveredIdPreviousFrame != id)
5420 g.HoveredIdTimer = g.HoveredIdNotActiveTimer = 0.0f;
5421}
5422
5423ImGuiID ImGui::GetHoveredID()
5424{
5425 ImGuiContext &g = *GImGui;
5426 return g.HoveredId ? g.HoveredId : g.HoveredIdPreviousFrame;
5427}
5428
5429void ImGui::MarkItemEdited(ImGuiID id)
5430{
5431 // This marking is to be able to provide info for IsItemDeactivatedAfterEdit().
5432 // ActiveId might have been released by the time we call this (as in the typical press/release button behavior) but
5433 // still need to fill the data.
5434 ImGuiContext &g = *GImGui;
5435 if (g.LastItemData.ItemFlags & ImGuiItemFlags_NoMarkEdited)
5436 return;
5437 if (g.ActiveId == id || g.ActiveId == 0)
5438 {
5439 // FIXME: Can't we fully rely on LastItemData yet?
5440 g.ActiveIdHasBeenEditedThisFrame = true;
5441 g.ActiveIdHasBeenEditedBefore = true;
5442 if (g.DeactivatedItemData.ID == id)
5443 g.DeactivatedItemData.HasBeenEditedBefore = true;
5444 }
5445
5446 // We accept a MarkItemEdited() on drag and drop targets (see
5447 // https://github.com/ocornut/imgui/issues/1875#issuecomment-978243343) We accept 'ActiveIdPreviousFrame == id' for
5448 // InputText() returning an edit after it has been taken ActiveId away (#4714)
5449 IM_ASSERT(g.DragDropActive || g.ActiveId == id || g.ActiveId == 0 || g.ActiveIdPreviousFrame == id ||
5450 (g.CurrentMultiSelect != NULL && g.BoxSelectState.IsActive));
5451
5452 // IM_ASSERT(g.CurrentWindow->DC.LastItemId == id);
5453 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Edited;
5454}
5455
5456bool ImGui::IsWindowContentHoverable(ImGuiWindow *window, ImGuiHoveredFlags flags)
5457{
5458 // An active popup disable hovering on other windows (apart from its own children)
5459 // FIXME-OPT: This could be cached/stored within the window.
5460 ImGuiContext &g = *GImGui;
5461 if (g.NavWindow)
5462 if (ImGuiWindow *focused_root_window = g.NavWindow->RootWindowDockTree)
5463 if (focused_root_window->WasActive && focused_root_window != window->RootWindowDockTree)
5464 {
5465 // For the purpose of those flags we differentiate "standard popup" from "modal popup"
5466 // NB: The 'else' is important because Modal windows are also Popups.
5467 bool want_inhibit = false;
5468 if (focused_root_window->Flags & ImGuiWindowFlags_Modal)
5469 want_inhibit = true;
5470 else if ((focused_root_window->Flags & ImGuiWindowFlags_Popup) &&
5471 !(flags & ImGuiHoveredFlags_AllowWhenBlockedByPopup))
5472 want_inhibit = true;
5473
5474 // Inhibit hover unless the window is within the stack of our modal/popup
5475 if (want_inhibit)
5476 if (!IsWindowWithinBeginStackOf(window->RootWindow, focused_root_window))
5477 return false;
5478 }
5479
5480 // Filter by viewport
5481 if (window->Viewport != g.MouseViewport)
5482 if (g.MovingWindow == NULL || window->RootWindowDockTree != g.MovingWindow->RootWindowDockTree)
5483 return false;
5484
5485 return true;
5486}
5487
5488static inline float CalcDelayFromHoveredFlags(ImGuiHoveredFlags flags)
5489{
5490 ImGuiContext &g = *GImGui;
5491 if (flags & ImGuiHoveredFlags_DelayNormal)
5492 return g.Style.HoverDelayNormal;
5493 if (flags & ImGuiHoveredFlags_DelayShort)
5494 return g.Style.HoverDelayShort;
5495 return 0.0f;
5496}
5497
5498static ImGuiHoveredFlags ApplyHoverFlagsForTooltip(ImGuiHoveredFlags user_flags, ImGuiHoveredFlags shared_flags)
5499{
5500 // Allow instance flags to override shared flags
5501 if (user_flags & (ImGuiHoveredFlags_DelayNone | ImGuiHoveredFlags_DelayShort | ImGuiHoveredFlags_DelayNormal))
5502 shared_flags &= ~(ImGuiHoveredFlags_DelayNone | ImGuiHoveredFlags_DelayShort | ImGuiHoveredFlags_DelayNormal);
5503 return user_flags | shared_flags;
5504}
5505
5506// This is roughly matching the behavior of internal-facing ItemHoverable()
5507// - we allow hovering to be true when ActiveId==window->MoveID, so that clicking on non-interactive items such as a
5508// Text() item still returns true with IsItemHovered()
5509// - this should work even for non-interactive items that have no ID, so we cannot use LastItemId
5510bool ImGui::IsItemHovered(ImGuiHoveredFlags flags)
5511{
5512 ImGuiContext &g = *GImGui;
5513 ImGuiWindow *window = g.CurrentWindow;
5514 IM_ASSERT_USER_ERROR((flags & ~ImGuiHoveredFlags_AllowedMaskForIsItemHovered) == 0,
5515 "Invalid flags for IsItemHovered()!");
5516
5517 if (g.NavHighlightItemUnderNav && g.NavCursorVisible && !(flags & ImGuiHoveredFlags_NoNavOverride))
5518 {
5519 if (!IsItemFocused())
5520 return false;
5521 if ((g.LastItemData.ItemFlags & ImGuiItemFlags_Disabled) && !(flags & ImGuiHoveredFlags_AllowWhenDisabled))
5522 return false;
5523
5524 if (flags & ImGuiHoveredFlags_ForTooltip)
5525 flags = ApplyHoverFlagsForTooltip(flags, g.Style.HoverFlagsForTooltipNav);
5526 }
5527 else
5528 {
5529 // Test for bounding box overlap, as updated as ItemAdd()
5530 ImGuiItemStatusFlags status_flags = g.LastItemData.StatusFlags;
5531 if (!(status_flags & ImGuiItemStatusFlags_HoveredRect))
5532 return false;
5533
5534 if (flags & ImGuiHoveredFlags_ForTooltip)
5535 flags = ApplyHoverFlagsForTooltip(flags, g.Style.HoverFlagsForTooltipMouse);
5536
5537 // Done with rectangle culling so we can perform heavier checks now
5538 // Test if we are hovering the right window (our window could be behind another window)
5539 // [2021/03/02] Reworked / reverted the revert, finally. Note we want e.g. BeginGroup/ItemAdd/EndGroup to work
5540 // as well. (#3851) [2017/10/16] Reverted commit 344d48be3 and testing RootWindow instead. I believe it is
5541 // correct to NOT test for RootWindow but this leaves us unable to use IsItemHovered() after EndChild() itself.
5542 // Until a solution is found I believe reverting to the test from 2017/09/27 is safe since this was the test
5543 // that has been running for a long while.
5544 if (g.HoveredWindow != window && (status_flags & ImGuiItemStatusFlags_HoveredWindow) == 0)
5545 if ((flags & ImGuiHoveredFlags_AllowWhenOverlappedByWindow) == 0)
5546 return false;
5547
5548 // Test if another item is active (e.g. being dragged)
5549 const ImGuiID id = g.LastItemData.ID;
5550 if ((flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem) == 0)
5551 if (g.ActiveId != 0 && g.ActiveId != id && !g.ActiveIdAllowOverlap)
5552 if (g.ActiveId != window->MoveId && g.ActiveId != window->TabId)
5553 return false;
5554
5555 // Test if interactions on this window are blocked by an active popup or modal.
5556 // The ImGuiHoveredFlags_AllowWhenBlockedByPopup flag will be tested here.
5557 if (!IsWindowContentHoverable(window, flags) &&
5558 !(g.LastItemData.ItemFlags & ImGuiItemFlags_NoWindowHoverableCheck))
5559 return false;
5560
5561 // Test if the item is disabled
5562 if ((g.LastItemData.ItemFlags & ImGuiItemFlags_Disabled) && !(flags & ImGuiHoveredFlags_AllowWhenDisabled))
5563 return false;
5564
5565 // Special handling for calling after Begin() which represent the title bar or tab.
5566 // When the window is skipped/collapsed (SkipItems==true) that last item (always ->MoveId submitted by Begin)
5567 // will never be overwritten so we need to detect the case.
5568 if (id == window->MoveId && window->WriteAccessed)
5569 return false;
5570
5571 // Test if using AllowOverlap and overlapped
5572 if ((g.LastItemData.ItemFlags & ImGuiItemFlags_AllowOverlap) && id != 0)
5573 if ((flags & ImGuiHoveredFlags_AllowWhenOverlappedByItem) == 0)
5574 if (g.HoveredIdPreviousFrame != g.LastItemData.ID)
5575 return false;
5576 }
5577
5578 // Handle hover delay
5579 // (some ideas: https://www.nngroup.com/articles/timing-exposing-content)
5580 const float delay = CalcDelayFromHoveredFlags(flags);
5581 if (delay > 0.0f || (flags & ImGuiHoveredFlags_Stationary))
5582 {
5583 ImGuiID hover_delay_id =
5584 (g.LastItemData.ID != 0) ? g.LastItemData.ID : window->GetIDFromPos(g.LastItemData.Rect.Min);
5585 if ((flags & ImGuiHoveredFlags_NoSharedDelay) && (g.HoverItemDelayIdPreviousFrame != hover_delay_id))
5586 g.HoverItemDelayTimer = 0.0f;
5587 g.HoverItemDelayId = hover_delay_id;
5588
5589 // When changing hovered item we requires a bit of stationary delay before activating hover timer,
5590 // but once unlocked on a given item we also moving.
5591 // if (g.HoverDelayTimer >= delay && (g.HoverDelayTimer - g.IO.DeltaTime < delay || g.MouseStationaryTimer -
5592 // g.IO.DeltaTime < g.Style.HoverStationaryDelay)) { IMGUI_DEBUG_LOG("HoverDelayTimer = %f/%f,
5593 // MouseStationaryTimer = %f\n", g.HoverDelayTimer, delay, g.MouseStationaryTimer); }
5594 if ((flags & ImGuiHoveredFlags_Stationary) != 0 && g.HoverItemUnlockedStationaryId != hover_delay_id)
5595 return false;
5596
5597 if (g.HoverItemDelayTimer < delay)
5598 return false;
5599 }
5600
5601 return true;
5602}
5603
5604// Internal facing ItemHoverable() used when submitting widgets. Differs slightly from IsItemHovered().
5605// (this does not rely on LastItemData it can be called from a ButtonBehavior() call not following an ItemAdd() call)
5606// FIXME-LEGACY: the 'ImGuiItemFlags item_flags' parameter was added on 2023-06-28.
5607// If you used this in your legacy/custom widgets code:
5608// - Commonly: if your ItemHoverable() call comes after an ItemAdd() call: pass 'item_flags = g.LastItemData.ItemFlags'.
5609// - Rare: otherwise you may pass 'item_flags = 0' (ImGuiItemFlags_None) unless you want to benefit from special
5610// behavior handled by ItemHoverable.
5611bool ImGui::ItemHoverable(const ImRect &bb, ImGuiID id, ImGuiItemFlags item_flags)
5612{
5613 ImGuiContext &g = *GImGui;
5614 ImGuiWindow *window = g.CurrentWindow;
5615
5616 // Detect ID conflicts
5617#ifndef IMGUI_DISABLE_DEBUG_TOOLS
5618 if (id != 0 && g.HoveredIdPreviousFrame == id && (item_flags & ImGuiItemFlags_AllowDuplicateId) == 0)
5619 {
5620 g.HoveredIdPreviousFrameItemCount++;
5621 if (g.DebugDrawIdConflicts == id)
5622 window->DrawList->AddRect(bb.Min - ImVec2(1, 1), bb.Max + ImVec2(1, 1), IM_COL32(255, 0, 0, 255), 0.0f,
5623 ImDrawFlags_None, 2.0f);
5624 }
5625#endif
5626
5627 if (g.HoveredWindow != window)
5628 return false;
5629 if (!IsMouseHoveringRect(bb.Min, bb.Max))
5630 return false;
5631
5632 if (g.HoveredId != 0 && g.HoveredId != id && !g.HoveredIdAllowOverlap)
5633 return false;
5634 if (g.ActiveId != 0 && g.ActiveId != id && !g.ActiveIdAllowOverlap)
5635 if (!g.ActiveIdFromShortcut)
5636 return false;
5637
5638 // Done with rectangle culling so we can perform heavier checks now.
5639 if (!(item_flags & ImGuiItemFlags_NoWindowHoverableCheck) &&
5640 !IsWindowContentHoverable(window, ImGuiHoveredFlags_None))
5641 {
5642 g.HoveredIdIsDisabled = true;
5643 return false;
5644 }
5645
5646 // We exceptionally allow this function to be called with id==0 to allow using it for easy high-level
5647 // hover test in widgets code. We could also decide to split this function is two.
5648 if (id != 0)
5649 {
5650 // Drag source doesn't report as hovered
5651 if (g.DragDropActive && g.DragDropPayload.SourceId == id &&
5652 !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoDisableHover))
5653 return false;
5654
5655 SetHoveredID(id);
5656
5657 // AllowOverlap mode (rarely used) requires previous frame HoveredId to be null or to match.
5658 // This allows using patterns where a later submitted widget overlaps a previous one. Generally perceived as a
5659 // front-to-back hit-test.
5660 if (item_flags & ImGuiItemFlags_AllowOverlap)
5661 {
5662 g.HoveredIdAllowOverlap = true;
5663 if (g.HoveredIdPreviousFrame != id)
5664 return false;
5665 }
5666
5667 // Display shortcut (only works with mouse)
5668 // (ImGuiItemStatusFlags_HasShortcut in LastItemData denotes we want a tooltip)
5669 if (id == g.LastItemData.ID && (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasShortcut) &&
5670 g.ActiveId != id)
5671 if (IsItemHovered(ImGuiHoveredFlags_ForTooltip | ImGuiHoveredFlags_DelayNormal))
5672 SetTooltip("%s", GetKeyChordName(g.LastItemData.Shortcut));
5673 }
5674
5675 // When disabled we'll return false but still set HoveredId
5676 if (item_flags & ImGuiItemFlags_Disabled)
5677 {
5678 // Release active id if turning disabled
5679 if (g.ActiveId == id && id != 0)
5680 ClearActiveID();
5681 g.HoveredIdIsDisabled = true;
5682 return false;
5683 }
5684
5685#ifndef IMGUI_DISABLE_DEBUG_TOOLS
5686 if (id != 0)
5687 {
5688 // [DEBUG] Item Picker tool!
5689 // We perform the check here because reaching is path is rare (1~ time a frame),
5690 // making the cost of this tool near-zero! We could get better call-stack and support picking non-hovered
5691 // items if we performed the test in ItemAdd(), but that would incur a bigger runtime cost.
5692 if (g.DebugItemPickerActive && g.HoveredIdPreviousFrame == id)
5693 GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(255, 255, 0, 255));
5694 if (g.DebugItemPickerBreakId == id)
5695 IM_DEBUG_BREAK();
5696 }
5697#endif
5698
5699 if (g.NavHighlightItemUnderNav && (item_flags & ImGuiItemFlags_NoNavDisableMouseHover) == 0)
5700 return false;
5701
5702 return true;
5703}
5704
5705// FIXME: This is inlined/duplicated in ItemAdd()
5706// FIXME: The id != 0 path is not used by our codebase, may get rid of it?
5707bool ImGui::IsClippedEx(const ImRect &bb, ImGuiID id)
5708{
5709 ImGuiContext &g = *GImGui;
5710 ImGuiWindow *window = g.CurrentWindow;
5711 if (!bb.Overlaps(window->ClipRect))
5712 if (id == 0 || (id != g.ActiveId && id != g.ActiveIdPreviousFrame && id != g.NavId && id != g.NavActivateId))
5713 if (!g.ItemUnclipByLog)
5714 return true;
5715 return false;
5716}
5717
5718// This is also inlined in ItemAdd()
5719// Note: if ImGuiItemStatusFlags_HasDisplayRect is set, user needs to set g.LastItemData.DisplayRect.
5720void ImGui::SetLastItemData(ImGuiID item_id, ImGuiItemFlags item_flags, ImGuiItemStatusFlags status_flags,
5721 const ImRect &item_rect)
5722{
5723 ImGuiContext &g = *GImGui;
5724 g.LastItemData.ID = item_id;
5725 g.LastItemData.ItemFlags = item_flags;
5726 g.LastItemData.StatusFlags = status_flags;
5727 g.LastItemData.Rect = g.LastItemData.NavRect = item_rect;
5728}
5729
5730static void ImGui::SetLastItemDataForWindow(ImGuiWindow *window, const ImRect &rect)
5731{
5732 ImGuiContext &g = *GImGui;
5733 if (window->DockIsActive)
5734 SetLastItemData(window->MoveId, g.CurrentItemFlags, window->DC.DockTabItemStatusFlags,
5735 window->DC.DockTabItemRect);
5736 else
5737 SetLastItemData(window->MoveId, g.CurrentItemFlags, window->DC.WindowItemStatusFlags, rect);
5738}
5739
5740static void ImGui::SetLastItemDataForChildWindowItem(ImGuiWindow *window, const ImRect &rect)
5741{
5742 ImGuiContext &g = *GImGui;
5743 SetLastItemData(window->ChildId, g.CurrentItemFlags, window->DC.ChildItemStatusFlags, rect);
5744}
5745
5746float ImGui::CalcWrapWidthForPos(const ImVec2 &pos, float wrap_pos_x)
5747{
5748 if (wrap_pos_x < 0.0f)
5749 return 0.0f;
5750
5751 ImGuiContext &g = *GImGui;
5752 ImGuiWindow *window = g.CurrentWindow;
5753 if (wrap_pos_x == 0.0f)
5754 {
5755 // We could decide to setup a default wrapping max point for auto-resizing windows,
5756 // or have auto-wrap (with unspecified wrapping pos) behave as a ContentSize extending function?
5757 // if (window->Hidden && (window->Flags & ImGuiWindowFlags_AlwaysAutoResize))
5758 // wrap_pos_x = ImMax(window->WorkRect.Min.x + g.FontSize * 10.0f, window->WorkRect.Max.x);
5759 // else
5760 wrap_pos_x = window->WorkRect.Max.x;
5761 }
5762 else if (wrap_pos_x > 0.0f)
5763 {
5764 wrap_pos_x += window->Pos.x - window->Scroll.x; // wrap_pos_x is provided is window local space
5765 }
5766
5767 return ImMax(wrap_pos_x - pos.x, 1.0f);
5768}
5769
5770// IM_ALLOC() == ImGui::MemAlloc()
5771void *ImGui::MemAlloc(size_t size)
5772{
5773 void *ptr = (*GImAllocatorAllocFunc)(size, GImAllocatorUserData);
5774#ifndef IMGUI_DISABLE_DEBUG_TOOLS
5775 if (ImGuiContext *ctx = GImGui)
5776 DebugAllocHook(&ctx->DebugAllocInfo, ctx->FrameCount, ptr, size);
5777#endif
5778 return ptr;
5779}
5780
5781// IM_FREE() == ImGui::MemFree()
5782void ImGui::MemFree(void *ptr)
5783{
5784#ifndef IMGUI_DISABLE_DEBUG_TOOLS
5785 if (ptr != NULL)
5786 if (ImGuiContext *ctx = GImGui)
5787 DebugAllocHook(&ctx->DebugAllocInfo, ctx->FrameCount, ptr, (size_t)-1);
5788#endif
5789 return (*GImAllocatorFreeFunc)(ptr, GImAllocatorUserData);
5790}
5791
5792// We record the number of allocation in recent frames, as a way to audit/sanitize our guiding principles of "no
5793// allocations on idle/repeating frames"
5794void ImGui::DebugAllocHook(ImGuiDebugAllocInfo *info, int frame_count, void *ptr, size_t size)
5795{
5796 ImGuiDebugAllocEntry *entry = &info->LastEntriesBuf[info->LastEntriesIdx];
5797 IM_UNUSED(ptr);
5798 if (entry->FrameCount != frame_count)
5799 {
5800 info->LastEntriesIdx = (info->LastEntriesIdx + 1) % IM_ARRAYSIZE(info->LastEntriesBuf);
5801 entry = &info->LastEntriesBuf[info->LastEntriesIdx];
5802 entry->FrameCount = frame_count;
5803 entry->AllocCount = entry->FreeCount = 0;
5804 }
5805 if (size != (size_t)-1)
5806 {
5807 // printf("[%05d] MemAlloc(%d) -> 0x%p\n", frame_count, (int)size, ptr);
5808 entry->AllocCount++;
5809 info->TotalAllocCount++;
5810 }
5811 else
5812 {
5813 // printf("[%05d] MemFree(0x%p)\n", frame_count, ptr);
5814 entry->FreeCount++;
5815 info->TotalFreeCount++;
5816 }
5817}
5818
5819const char *ImGui::GetClipboardText()
5820{
5821 ImGuiContext &g = *GImGui;
5822 return g.PlatformIO.Platform_GetClipboardTextFn ? g.PlatformIO.Platform_GetClipboardTextFn(&g) : "";
5823}
5824
5825void ImGui::SetClipboardText(const char *text)
5826{
5827 ImGuiContext &g = *GImGui;
5828 if (g.PlatformIO.Platform_SetClipboardTextFn != NULL)
5829 g.PlatformIO.Platform_SetClipboardTextFn(&g, text);
5830}
5831
5832const char *ImGui::GetVersion()
5833{
5834 return IMGUI_VERSION;
5835}
5836
5837ImGuiIO &ImGui::GetIO()
5838{
5839 IM_ASSERT(GImGui != NULL &&
5840 "No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext() ?");
5841 return GImGui->IO;
5842}
5843
5844// This variant exists to facilitate backends experimenting with multi-threaded parallel context. (#8069, #6293, #5856)
5845ImGuiIO &ImGui::GetIO(ImGuiContext *ctx)
5846{
5847 IM_ASSERT(ctx != NULL);
5848 return ctx->IO;
5849}
5850
5851ImGuiPlatformIO &ImGui::GetPlatformIO()
5852{
5853 IM_ASSERT(GImGui != NULL &&
5854 "No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext()?");
5855 return GImGui->PlatformIO;
5856}
5857
5858// This variant exists to facilitate backends experimenting with multi-threaded parallel context. (#8069, #6293, #5856)
5859ImGuiPlatformIO &ImGui::GetPlatformIO(ImGuiContext *ctx)
5860{
5861 IM_ASSERT(ctx != NULL);
5862 return ctx->PlatformIO;
5863}
5864
5865// Pass this to your backend rendering function! Valid after Render() and until the next call to NewFrame()
5866ImDrawData *ImGui::GetDrawData()
5867{
5868 ImGuiContext &g = *GImGui;
5869 ImGuiViewportP *viewport = g.Viewports[0];
5870 return viewport->DrawDataP.Valid ? &viewport->DrawDataP : NULL;
5871}
5872
5873double ImGui::GetTime()
5874{
5875 return GImGui->Time;
5876}
5877
5878int ImGui::GetFrameCount()
5879{
5880 return GImGui->FrameCount;
5881}
5882
5883static ImDrawList *GetViewportBgFgDrawList(ImGuiViewportP *viewport, size_t drawlist_no, const char *drawlist_name)
5884{
5885 // Create the draw list on demand, because they are not frequently used for all viewports
5886 ImGuiContext &g = *GImGui;
5887 IM_ASSERT(drawlist_no < IM_ARRAYSIZE(viewport->BgFgDrawLists));
5888 ImDrawList *draw_list = viewport->BgFgDrawLists[drawlist_no];
5889 if (draw_list == NULL)
5890 {
5891 draw_list = IM_NEW(ImDrawList)(&g.DrawListSharedData);
5892 draw_list->_OwnerName = drawlist_name;
5893 viewport->BgFgDrawLists[drawlist_no] = draw_list;
5894 }
5895
5896 // Our ImDrawList system requires that there is always a command
5897 if (viewport->BgFgDrawListsLastFrame[drawlist_no] != g.FrameCount)
5898 {
5899 draw_list->_ResetForNewFrame();
5900 draw_list->PushTextureID(g.IO.Fonts->TexID);
5901 draw_list->PushClipRect(viewport->Pos, viewport->Pos + viewport->Size, false);
5902 viewport->BgFgDrawListsLastFrame[drawlist_no] = g.FrameCount;
5903 }
5904 return draw_list;
5905}
5906
5907ImDrawList *ImGui::GetBackgroundDrawList(ImGuiViewport *viewport)
5908{
5909 if (viewport == NULL)
5910 viewport = GImGui->CurrentWindow->Viewport;
5911 return GetViewportBgFgDrawList((ImGuiViewportP *)viewport, 0, "##Background");
5912}
5913
5914ImDrawList *ImGui::GetForegroundDrawList(ImGuiViewport *viewport)
5915{
5916 if (viewport == NULL)
5917 viewport = GImGui->CurrentWindow->Viewport;
5918 return GetViewportBgFgDrawList((ImGuiViewportP *)viewport, 1, "##Foreground");
5919}
5920
5921ImDrawListSharedData *ImGui::GetDrawListSharedData()
5922{
5923 return &GImGui->DrawListSharedData;
5924}
5925
5926void ImGui::StartMouseMovingWindow(ImGuiWindow *window)
5927{
5928 // Set ActiveId even if the _NoMove flag is set. Without it, dragging away from a window with _NoMove would activate
5929 // hover on other windows. We _also_ call this when clicking in a window empty space when
5930 // io.ConfigWindowsMoveFromTitleBarOnly is set, but clear g.MovingWindow afterward. This is because we want ActiveId
5931 // to be set even when the window is not permitted to move.
5932 ImGuiContext &g = *GImGui;
5933 FocusWindow(window);
5934 SetActiveID(window->MoveId, window);
5935 if (g.IO.ConfigNavCursorVisibleAuto)
5936 g.NavCursorVisible = false;
5937 g.ActiveIdClickOffset = g.IO.MouseClickedPos[0] - window->RootWindowDockTree->Pos;
5938 g.ActiveIdNoClearOnFocusLoss = true;
5939 SetActiveIdUsingAllKeyboardKeys();
5940
5941 bool can_move_window = true;
5942 if ((window->Flags & ImGuiWindowFlags_NoMove) || (window->RootWindowDockTree->Flags & ImGuiWindowFlags_NoMove))
5943 can_move_window = false;
5944 if (ImGuiDockNode *node = window->DockNodeAsHost)
5945 if (node->VisibleWindow && (node->VisibleWindow->Flags & ImGuiWindowFlags_NoMove))
5946 can_move_window = false;
5947 if (can_move_window)
5948 g.MovingWindow = window;
5949}
5950
5951// We use 'undock == false' when dragging from title bar to allow moving groups of floating nodes without undocking
5952// them.
5953void ImGui::StartMouseMovingWindowOrNode(ImGuiWindow *window, ImGuiDockNode *node, bool undock)
5954{
5955 ImGuiContext &g = *GImGui;
5956 bool can_undock_node = false;
5957 if (undock && node != NULL && node->VisibleWindow && (node->VisibleWindow->Flags & ImGuiWindowFlags_NoMove) == 0 &&
5958 (node->MergedFlags & ImGuiDockNodeFlags_NoUndocking) == 0)
5959 {
5960 // Can undock if:
5961 // - part of a hierarchy with more than one visible node (if only one is visible, we'll just move the root
5962 // window)
5963 // - part of a dockspace node hierarchy: so we can undock the last single visible node too. Undocking from a
5964 // fixed/central node will create a new node and copy windows.
5965 ImGuiDockNode *root_node = DockNodeGetRootNode(node);
5966 if (root_node->OnlyNodeWithWindows != node ||
5967 root_node->CentralNode !=
5968 NULL) // -V1051 PVS-Studio thinks node should be root_node and is wrong about that.
5969 can_undock_node = true;
5970 }
5971
5972 const bool clicked = IsMouseClicked(0);
5973 const bool dragging = IsMouseDragging(0);
5974 if (can_undock_node && dragging)
5975 DockContextQueueUndockNode(
5976 &g,
5977 node); // Will lead to DockNodeStartMouseMovingWindow() -> StartMouseMovingWindow() being called next frame
5978 else if (!can_undock_node && (clicked || dragging) && g.MovingWindow != window)
5979 StartMouseMovingWindow(window);
5980}
5981
5982// Handle mouse moving window
5983// Note: moving window with the navigation keys (Square + d-pad / CTRL+TAB + Arrows) are processed in
5984// NavUpdateWindowing()
5985// FIXME: We don't have strong guarantee that g.MovingWindow stay synced with g.ActiveId == g.MovingWindow->MoveId.
5986// This is currently enforced by the fact that BeginDragDropSource() is setting all g.ActiveIdUsingXXXX flags to inhibit
5987// navigation inputs, but if we should more thoroughly test cases where g.ActiveId or g.MovingWindow gets changed and
5988// not the other.
5989void ImGui::UpdateMouseMovingWindowNewFrame()
5990{
5991 ImGuiContext &g = *GImGui;
5992 if (g.MovingWindow != NULL)
5993 {
5994 // We actually want to move the root window. g.MovingWindow == window we clicked on (could be a child window).
5995 // We track it to preserve Focus and so that generally ActiveIdWindow == MovingWindow and ActiveId ==
5996 // MovingWindow->MoveId for consistency.
5997 KeepAliveID(g.ActiveId);
5998 IM_ASSERT(g.MovingWindow && g.MovingWindow->RootWindowDockTree);
5999 ImGuiWindow *moving_window = g.MovingWindow->RootWindowDockTree;
6000
6001 // When a window stop being submitted while being dragged, it may will its viewport until next Begin()
6002 const bool window_disappared = (!moving_window->WasActive && !moving_window->Active);
6003 if (g.IO.MouseDown[0] && IsMousePosValid(&g.IO.MousePos) && !window_disappared)
6004 {
6005 ImVec2 pos = g.IO.MousePos - g.ActiveIdClickOffset;
6006 if (moving_window->Pos.x != pos.x || moving_window->Pos.y != pos.y)
6007 {
6008 SetWindowPos(moving_window, pos, ImGuiCond_Always);
6009 if (moving_window->Viewport &&
6010 moving_window->ViewportOwned) // Synchronize viewport immediately because some overlays may relies
6011 // on clipping rectangle before we Begin() into the window.
6012 {
6013 moving_window->Viewport->Pos = pos;
6014 moving_window->Viewport->UpdateWorkRect();
6015 }
6016 }
6017 FocusWindow(g.MovingWindow);
6018 }
6019 else
6020 {
6021 if (!window_disappared)
6022 {
6023 // Try to merge the window back into the main viewport.
6024 // This works because MouseViewport should be != MovingWindow->Viewport on release (as per code in
6025 // UpdateViewports)
6026 if (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable)
6027 UpdateTryMergeWindowIntoHostViewport(moving_window, g.MouseViewport);
6028
6029 // Restore the mouse viewport so that we don't hover the viewport _under_ the moved window during the
6030 // frame we released the mouse button.
6031 if (moving_window->Viewport && !IsDragDropPayloadBeingAccepted())
6032 g.MouseViewport = moving_window->Viewport;
6033
6034 // Clear the NoInput window flag set by the Viewport system
6035 if (moving_window->Viewport)
6036 moving_window->Viewport->Flags &= ~ImGuiViewportFlags_NoInputs;
6037 }
6038
6039 g.MovingWindow = NULL;
6040 ClearActiveID();
6041 }
6042 }
6043 else
6044 {
6045 // When clicking/dragging from a window that has the _NoMove flag, we still set the ActiveId in order to prevent
6046 // hovering others.
6047 if (g.ActiveIdWindow && g.ActiveIdWindow->MoveId == g.ActiveId)
6048 {
6049 KeepAliveID(g.ActiveId);
6050 if (!g.IO.MouseDown[0])
6051 ClearActiveID();
6052 }
6053 }
6054}
6055
6056// Initiate focusing and moving window when clicking on empty space or title bar.
6057// Initiate focusing window when clicking on a disabled item.
6058// Handle left-click and right-click focus.
6059void ImGui::UpdateMouseMovingWindowEndFrame()
6060{
6061 ImGuiContext &g = *GImGui;
6062 if (g.ActiveId != 0 || (g.HoveredId != 0 && !g.HoveredIdIsDisabled))
6063 return;
6064
6065 // Unless we just made a window/popup appear
6066 if (g.NavWindow && g.NavWindow->Appearing)
6067 return;
6068
6069 // Click on empty space to focus window and start moving
6070 // (after we're done with all our widgets, so e.g. clicking on docking tab-bar which have set HoveredId already and
6071 // not get us here!)
6072 if (g.IO.MouseClicked[0])
6073 {
6074 // Handle the edge case of a popup being closed while clicking in its empty space.
6075 // If we try to focus it, FocusWindow() > ClosePopupsOverWindow() will accidentally close any parent popups
6076 // because they are not linked together any more.
6077 ImGuiWindow *root_window = g.HoveredWindow ? g.HoveredWindow->RootWindow : NULL;
6078 const bool is_closed_popup = root_window && (root_window->Flags & ImGuiWindowFlags_Popup) &&
6079 !IsPopupOpen(root_window->PopupId, ImGuiPopupFlags_AnyPopupLevel);
6080
6081 if (root_window != NULL && !is_closed_popup)
6082 {
6083 StartMouseMovingWindow(g.HoveredWindow); //-V595
6084
6085 // Cancel moving if clicked outside of title bar
6086 if (g.IO.ConfigWindowsMoveFromTitleBarOnly)
6087 if (!(root_window->Flags & ImGuiWindowFlags_NoTitleBar) || root_window->DockIsActive)
6088 if (!root_window->TitleBarRect().Contains(g.IO.MouseClickedPos[0]))
6089 g.MovingWindow = NULL;
6090
6091 // Cancel moving if clicked over an item which was disabled or inhibited by popups
6092 // (when g.HoveredIdIsDisabled == true && g.HoveredId == 0 we are inhibited by popups, when
6093 // g.HoveredIdIsDisabled == true && g.HoveredId != 0 we are over a disabled item)0 already)
6094 if (g.HoveredIdIsDisabled)
6095 g.MovingWindow = NULL;
6096 }
6097 else if (root_window == NULL && g.NavWindow != NULL)
6098 {
6099 // Clicking on void disable focus
6100 FocusWindow(NULL, ImGuiFocusRequestFlags_UnlessBelowModal);
6101 }
6102 }
6103
6104 // With right mouse button we close popups without changing focus based on where the mouse is aimed
6105 // Instead, focus will be restored to the window under the bottom-most closed popup.
6106 // (The left mouse button path calls FocusWindow on the hovered window, which will lead
6107 // NewFrame->ClosePopupsOverWindow to trigger)
6108 if (g.IO.MouseClicked[1] && g.HoveredId == 0)
6109 {
6110 // Find the top-most window between HoveredWindow and the top-most Modal Window.
6111 // This is where we can trim the popup stack.
6112 ImGuiWindow *modal = GetTopMostPopupModal();
6113 bool hovered_window_above_modal = g.HoveredWindow && (modal == NULL || IsWindowAbove(g.HoveredWindow, modal));
6114 ClosePopupsOverWindow(hovered_window_above_modal ? g.HoveredWindow : modal, true);
6115 }
6116}
6117
6118// This is called during NewFrame()->UpdateViewportsNewFrame() only.
6119// Need to keep in sync with SetWindowPos()
6120static void TranslateWindow(ImGuiWindow *window, const ImVec2 &delta)
6121{
6122 window->Pos += delta;
6123 window->ClipRect.Translate(delta);
6124 window->OuterRectClipped.Translate(delta);
6125 window->InnerRect.Translate(delta);
6126 window->DC.CursorPos += delta;
6127 window->DC.CursorStartPos += delta;
6128 window->DC.CursorMaxPos += delta;
6129 window->DC.IdealMaxPos += delta;
6130}
6131
6132static void ScaleWindow(ImGuiWindow *window, float scale)
6133{
6134 ImVec2 origin = window->Viewport->Pos;
6135 window->Pos = ImFloor((window->Pos - origin) * scale + origin);
6136 window->Size = ImTrunc(window->Size * scale);
6137 window->SizeFull = ImTrunc(window->SizeFull * scale);
6138 window->ContentSize = ImTrunc(window->ContentSize * scale);
6139}
6140
6141static bool IsWindowActiveAndVisible(ImGuiWindow *window)
6142{
6143 return (window->Active) && (!window->Hidden);
6144}
6145
6146// The reason this is exposed in imgui_internal.h is: on touch-based system that don't have hovering, we want to
6147// dispatch inputs to the right target (imgui vs imgui+app)
6148void ImGui::UpdateHoveredWindowAndCaptureFlags(const ImVec2 &mouse_pos)
6149{
6150 ImGuiContext &g = *GImGui;
6151 ImGuiIO &io = g.IO;
6152
6153 // FIXME-DPI: This storage was added on 2021/03/31 for test engine, but if we want to multiply WINDOWS_HOVER_PADDING
6154 // by DpiScale, we need to make this window-agnostic anyhow, maybe need storing inside ImGuiWindow.
6155 g.WindowsBorderHoverPadding =
6156 ImMax(ImMax(g.Style.TouchExtraPadding.x, g.Style.TouchExtraPadding.y), g.Style.WindowBorderHoverPadding);
6157
6158 // Find the window hovered by mouse:
6159 // - Child windows can extend beyond the limit of their parent so we need to derive HoveredRootWindow from
6160 // HoveredWindow.
6161 // - When moving a window we can skip the search, which also conveniently bypasses the fact that
6162 // window->WindowRectClipped is lagging as this point of the frame.
6163 // - We also support the moved window toggling the NoInputs flag after moving has started in order to be able to
6164 // detect windows below it, which is useful for e.g. docking mechanisms.
6165 bool clear_hovered_windows = false;
6166 FindHoveredWindowEx(mouse_pos, false, &g.HoveredWindow, &g.HoveredWindowUnderMovingWindow);
6167 IM_ASSERT(g.HoveredWindow == NULL || g.HoveredWindow == g.MovingWindow ||
6168 g.HoveredWindow->Viewport == g.MouseViewport);
6169 g.HoveredWindowBeforeClear = g.HoveredWindow;
6170
6171 // Modal windows prevents mouse from hovering behind them.
6172 ImGuiWindow *modal_window = GetTopMostPopupModal();
6173 if (modal_window && g.HoveredWindow &&
6174 !IsWindowWithinBeginStackOf(g.HoveredWindow->RootWindow, modal_window)) // FIXME-MERGE: RootWindowDockTree ?
6175 clear_hovered_windows = true;
6176
6177 // Disabled mouse hovering (we don't currently clear MousePos, we could)
6178 if (io.ConfigFlags & ImGuiConfigFlags_NoMouse)
6179 clear_hovered_windows = true;
6180
6181 // We track click ownership. When clicked outside of a window the click is owned by the application and
6182 // won't report hovering nor request capture even while dragging over our windows afterward.
6183 const bool has_open_popup = (g.OpenPopupStack.Size > 0);
6184 const bool has_open_modal = (modal_window != NULL);
6185 int mouse_earliest_down = -1;
6186 bool mouse_any_down = false;
6187 for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++)
6188 {
6189 if (io.MouseClicked[i])
6190 {
6191 io.MouseDownOwned[i] = (g.HoveredWindow != NULL) || has_open_popup;
6192 io.MouseDownOwnedUnlessPopupClose[i] = (g.HoveredWindow != NULL) || has_open_modal;
6193 }
6194 mouse_any_down |= io.MouseDown[i];
6195 if (io.MouseDown[i] ||
6196 io.MouseReleased[i]) // Increase release frame for our evaluation of earliest button (#1392)
6197 if (mouse_earliest_down == -1 || io.MouseClickedTime[i] < io.MouseClickedTime[mouse_earliest_down])
6198 mouse_earliest_down = i;
6199 }
6200 const bool mouse_avail = (mouse_earliest_down == -1) || io.MouseDownOwned[mouse_earliest_down];
6201 const bool mouse_avail_unless_popup_close =
6202 (mouse_earliest_down == -1) || io.MouseDownOwnedUnlessPopupClose[mouse_earliest_down];
6203
6204 // If mouse was first clicked outside of ImGui bounds we also cancel out hovering.
6205 // FIXME: For patterns of drag and drop across OS windows, we may need to rework/remove this test (first committed
6206 // 311c0ca9 on 2015/02)
6207 const bool mouse_dragging_extern_payload =
6208 g.DragDropActive && (g.DragDropSourceFlags & ImGuiDragDropFlags_SourceExtern) != 0;
6209 if (!mouse_avail && !mouse_dragging_extern_payload)
6210 clear_hovered_windows = true;
6211
6212 if (clear_hovered_windows)
6213 g.HoveredWindow = g.HoveredWindowUnderMovingWindow = NULL;
6214
6215 // Update io.WantCaptureMouse for the user application (true = dispatch mouse info to Dear ImGui only, false =
6216 // dispatch mouse to Dear ImGui + underlying app) Update io.WantCaptureMouseAllowPopupClose (experimental) to give a
6217 // chance for app to react to popup closure with a drag
6218 if (g.WantCaptureMouseNextFrame != -1)
6219 {
6220 io.WantCaptureMouse = io.WantCaptureMouseUnlessPopupClose = (g.WantCaptureMouseNextFrame != 0);
6221 }
6222 else
6223 {
6224 io.WantCaptureMouse = (mouse_avail && (g.HoveredWindow != NULL || mouse_any_down)) || has_open_popup;
6225 io.WantCaptureMouseUnlessPopupClose =
6226 (mouse_avail_unless_popup_close && (g.HoveredWindow != NULL || mouse_any_down)) || has_open_modal;
6227 }
6228
6229 // Update io.WantCaptureKeyboard for the user application (true = dispatch keyboard info to Dear ImGui only, false =
6230 // dispatch keyboard info to Dear ImGui + underlying app)
6231 io.WantCaptureKeyboard = false;
6232 if ((io.ConfigFlags & ImGuiConfigFlags_NoKeyboard) == 0)
6233 {
6234 if ((g.ActiveId != 0) || (modal_window != NULL))
6235 io.WantCaptureKeyboard = true;
6236 else if (io.NavActive && (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) && io.ConfigNavCaptureKeyboard)
6237 io.WantCaptureKeyboard = true;
6238 }
6239 if (g.WantCaptureKeyboardNextFrame != -1) // Manual override
6240 io.WantCaptureKeyboard = (g.WantCaptureKeyboardNextFrame != 0);
6241
6242 // Update io.WantTextInput flag, this is to allow systems without a keyboard (e.g. mobile, hand-held) to show a
6243 // software keyboard if possible
6244 io.WantTextInput = (g.WantTextInputNextFrame != -1) ? (g.WantTextInputNextFrame != 0) : false;
6245}
6246
6247// Called once a frame. Followed by SetCurrentFont() which sets up the remaining data.
6248// FIXME-VIEWPORT: the concept of a single ClipRectFullscreen is not ideal!
6249static void SetupDrawListSharedData()
6250{
6251 ImGuiContext &g = *GImGui;
6252 ImRect virtual_space(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX);
6253 for (ImGuiViewportP *viewport : g.Viewports)
6254 virtual_space.Add(viewport->GetMainRect());
6255 g.DrawListSharedData.ClipRectFullscreen = virtual_space.ToVec4();
6256 g.DrawListSharedData.CurveTessellationTol = g.Style.CurveTessellationTol;
6257 g.DrawListSharedData.SetCircleTessellationMaxError(g.Style.CircleTessellationMaxError);
6258 g.DrawListSharedData.InitialFlags = ImDrawListFlags_None;
6259 if (g.Style.AntiAliasedLines)
6260 g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLines;
6261 if (g.Style.AntiAliasedLinesUseTex && !(g.IO.Fonts->Flags & ImFontAtlasFlags_NoBakedLines))
6262 g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLinesUseTex;
6263 if (g.Style.AntiAliasedFill)
6264 g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedFill;
6265 if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset)
6266 g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AllowVtxOffset;
6267 g.DrawListSharedData.InitialFringeScale = 1.0f; // FIXME-DPI: Change this for some DPI scaling experiments.
6268}
6269
6270void ImGui::NewFrame()
6271{
6272 IM_ASSERT(GImGui != NULL &&
6273 "No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext() ?");
6274 ImGuiContext &g = *GImGui;
6275
6276 // Remove pending delete hooks before frame start.
6277 // This deferred removal avoid issues of removal while iterating the hook vector
6278 for (int n = g.Hooks.Size - 1; n >= 0; n--)
6279 if (g.Hooks[n].Type == ImGuiContextHookType_PendingRemoval_)
6280 g.Hooks.erase(&g.Hooks[n]);
6281
6282 CallContextHooks(&g, ImGuiContextHookType_NewFramePre);
6283
6284 // Check and assert for various common IO and Configuration mistakes
6285 g.ConfigFlagsLastFrame = g.ConfigFlagsCurrFrame;
6286 ErrorCheckNewFrameSanityChecks();
6287 g.ConfigFlagsCurrFrame = g.IO.ConfigFlags;
6288
6289 // Load settings on first frame, save settings when modified (after a delay)
6290 UpdateSettings();
6291
6292 g.Time += g.IO.DeltaTime;
6293 g.WithinFrameScope = true;
6294 g.FrameCount += 1;
6295 g.TooltipOverrideCount = 0;
6296 g.WindowsActiveCount = 0;
6297 g.MenusIdSubmittedThisFrame.resize(0);
6298
6299 // Calculate frame-rate for the user, as a purely luxurious feature
6300 g.FramerateSecPerFrameAccum += g.IO.DeltaTime - g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx];
6301 g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx] = g.IO.DeltaTime;
6302 g.FramerateSecPerFrameIdx = (g.FramerateSecPerFrameIdx + 1) % IM_ARRAYSIZE(g.FramerateSecPerFrame);
6303 g.FramerateSecPerFrameCount = ImMin(g.FramerateSecPerFrameCount + 1, IM_ARRAYSIZE(g.FramerateSecPerFrame));
6304 g.IO.Framerate = (g.FramerateSecPerFrameAccum > 0.0f)
6305 ? (1.0f / (g.FramerateSecPerFrameAccum / (float)g.FramerateSecPerFrameCount))
6306 : FLT_MAX;
6307
6308 // Process input queue (trickle as many events as possible), turn events into writes to IO structure
6309 g.InputEventsTrail.resize(0);
6310 UpdateInputEvents(g.IO.ConfigInputTrickleEventQueue);
6311
6312 // Update viewports (after processing input queue, so io.MouseHoveredViewport is set)
6313 UpdateViewportsNewFrame();
6314
6315 // Setup current font and draw list shared data
6316 // FIXME-VIEWPORT: the concept of a single ClipRectFullscreen is not ideal!
6317 g.IO.Fonts->Locked = true;
6318 SetupDrawListSharedData();
6319 SetCurrentFont(GetDefaultFont());
6320 IM_ASSERT(g.Font->IsLoaded());
6321
6322 // Mark rendering data as invalid to prevent user who may have a handle on it to use it.
6323 for (ImGuiViewportP *viewport : g.Viewports)
6324 {
6325 viewport->DrawData = NULL;
6326 viewport->DrawDataP.Valid = false;
6327 }
6328
6329 // Drag and drop keep the source ID alive so even if the source disappear our state is consistent
6330 if (g.DragDropActive && g.DragDropPayload.SourceId == g.ActiveId)
6331 KeepAliveID(g.DragDropPayload.SourceId);
6332
6333 // [DEBUG]
6334 if (!g.IO.ConfigDebugHighlightIdConflicts || !g.IO.KeyCtrl) // Count is locked while holding CTRL
6335 g.DebugDrawIdConflicts = 0;
6336 if (g.IO.ConfigDebugHighlightIdConflicts && g.HoveredIdPreviousFrameItemCount > 1)
6337 g.DebugDrawIdConflicts = g.HoveredIdPreviousFrame;
6338
6339 // Update HoveredId data
6340 if (!g.HoveredIdPreviousFrame)
6341 g.HoveredIdTimer = 0.0f;
6342 if (!g.HoveredIdPreviousFrame || (g.HoveredId && g.ActiveId == g.HoveredId))
6343 g.HoveredIdNotActiveTimer = 0.0f;
6344 if (g.HoveredId)
6345 g.HoveredIdTimer += g.IO.DeltaTime;
6346 if (g.HoveredId && g.ActiveId != g.HoveredId)
6347 g.HoveredIdNotActiveTimer += g.IO.DeltaTime;
6348 g.HoveredIdPreviousFrame = g.HoveredId;
6349 g.HoveredIdPreviousFrameItemCount = 0;
6350 g.HoveredId = 0;
6351 g.HoveredIdAllowOverlap = false;
6352 g.HoveredIdIsDisabled = false;
6353
6354 // Clear ActiveID if the item is not alive anymore.
6355 // In 1.87, the common most call to KeepAliveID() was moved from GetID() to ItemAdd().
6356 // As a result, custom widget using ButtonBehavior() _without_ ItemAdd() need to call KeepAliveID() themselves.
6357 if (g.ActiveId != 0 && g.ActiveIdIsAlive != g.ActiveId && g.ActiveIdPreviousFrame == g.ActiveId)
6358 {
6359 IMGUI_DEBUG_LOG_ACTIVEID("NewFrame(): ClearActiveID() because it isn't marked alive anymore!\n");
6360 ClearActiveID();
6361 }
6362
6363 // Update ActiveId data (clear reference to active widget if the widget isn't alive anymore)
6364 if (g.ActiveId)
6365 g.ActiveIdTimer += g.IO.DeltaTime;
6366 g.LastActiveIdTimer += g.IO.DeltaTime;
6367 g.ActiveIdPreviousFrame = g.ActiveId;
6368 g.ActiveIdIsAlive = 0;
6369 g.ActiveIdHasBeenEditedThisFrame = false;
6370 g.ActiveIdIsJustActivated = false;
6371 if (g.TempInputId != 0 && g.ActiveId != g.TempInputId)
6372 g.TempInputId = 0;
6373 if (g.ActiveId == 0)
6374 {
6375 g.ActiveIdUsingNavDirMask = 0x00;
6376 g.ActiveIdUsingAllKeyboardKeys = false;
6377 }
6378 if (g.DeactivatedItemData.ElapseFrame < g.FrameCount)
6379 g.DeactivatedItemData.ID = 0;
6380 g.DeactivatedItemData.IsAlive = false;
6381
6382 // Record when we have been stationary as this state is preserved while over same item.
6383 // FIXME: The way this is expressed means user cannot alter HoverStationaryDelay during the frame to use varying
6384 // values. To allow this we should store HoverItemMaxStationaryTime+ID and perform the >= check in IsItemHovered()
6385 // function.
6386 if (g.HoverItemDelayId != 0 && g.MouseStationaryTimer >= g.Style.HoverStationaryDelay)
6387 g.HoverItemUnlockedStationaryId = g.HoverItemDelayId;
6388 else if (g.HoverItemDelayId == 0)
6389 g.HoverItemUnlockedStationaryId = 0;
6390 if (g.HoveredWindow != NULL && g.MouseStationaryTimer >= g.Style.HoverStationaryDelay)
6391 g.HoverWindowUnlockedStationaryId = g.HoveredWindow->ID;
6392 else if (g.HoveredWindow == NULL)
6393 g.HoverWindowUnlockedStationaryId = 0;
6394
6395 // Update hover delay for IsItemHovered() with delays and tooltips
6396 g.HoverItemDelayIdPreviousFrame = g.HoverItemDelayId;
6397 if (g.HoverItemDelayId != 0)
6398 {
6399 g.HoverItemDelayTimer += g.IO.DeltaTime;
6400 g.HoverItemDelayClearTimer = 0.0f;
6401 g.HoverItemDelayId = 0;
6402 }
6403 else if (g.HoverItemDelayTimer > 0.0f)
6404 {
6405 // This gives a little bit of leeway before clearing the hover timer, allowing mouse to cross gaps
6406 // We could expose 0.25f as style.HoverClearDelay but I am not sure of the logic yet, this is particularly
6407 // subtle.
6408 g.HoverItemDelayClearTimer += g.IO.DeltaTime;
6409 if (g.HoverItemDelayClearTimer >=
6410 ImMax(0.25f, g.IO.DeltaTime * 2.0f)) // ~7 frames at 30 Hz + allow for low framerate
6411 g.HoverItemDelayTimer = g.HoverItemDelayClearTimer =
6412 0.0f; // May want a decaying timer, in which case need to clamp at max first, based on max of caller
6413 // last requested timer.
6414 }
6415
6416 // Drag and drop
6417 g.DragDropAcceptIdPrev = g.DragDropAcceptIdCurr;
6418 g.DragDropAcceptIdCurr = 0;
6419 g.DragDropAcceptIdCurrRectSurface = FLT_MAX;
6420 g.DragDropWithinSource = false;
6421 g.DragDropWithinTarget = false;
6422 g.DragDropHoldJustPressedId = 0;
6423 g.TooltipPreviousWindow = NULL;
6424
6425 // Close popups on focus lost (currently wip/opt-in)
6426 // if (g.IO.AppFocusLost)
6427 // ClosePopupsExceptModals();
6428
6429 // Update keyboard input state
6430 UpdateKeyboardInputs();
6431
6432 // IM_ASSERT(g.IO.KeyCtrl == IsKeyDown(ImGuiKey_LeftCtrl) || IsKeyDown(ImGuiKey_RightCtrl));
6433 // IM_ASSERT(g.IO.KeyShift == IsKeyDown(ImGuiKey_LeftShift) || IsKeyDown(ImGuiKey_RightShift));
6434 // IM_ASSERT(g.IO.KeyAlt == IsKeyDown(ImGuiKey_LeftAlt) || IsKeyDown(ImGuiKey_RightAlt));
6435 // IM_ASSERT(g.IO.KeySuper == IsKeyDown(ImGuiKey_LeftSuper) || IsKeyDown(ImGuiKey_RightSuper));
6436
6437 // Update keyboard/gamepad navigation
6438 NavUpdate();
6439
6440 // Update mouse input state
6441 UpdateMouseInputs();
6442
6443 // Undocking
6444 // (needs to be before UpdateMouseMovingWindowNewFrame so the window is already offset and following the mouse on
6445 // the detaching frame)
6446 DockContextNewFrameUpdateUndocking(&g);
6447
6448 // Mark all windows as not visible and compact unused memory.
6449 IM_ASSERT(g.WindowsFocusOrder.Size <= g.Windows.Size);
6450 const float memory_compact_start_time = (g.GcCompactAll || g.IO.ConfigMemoryCompactTimer < 0.0f)
6451 ? FLT_MAX
6452 : (float)g.Time - g.IO.ConfigMemoryCompactTimer;
6453 for (ImGuiWindow *window : g.Windows)
6454 {
6455 window->WasActive = window->Active;
6456 window->Active = false;
6457 window->WriteAccessed = false;
6458 window->BeginCountPreviousFrame = window->BeginCount;
6459 window->BeginCount = 0;
6460
6461 // Garbage collect transient buffers of recently unused windows
6462 if (!window->WasActive && !window->MemoryCompacted && window->LastTimeActive < memory_compact_start_time)
6463 GcCompactTransientWindowBuffers(window);
6464 }
6465
6466 // Find hovered window
6467 // (needs to be before UpdateMouseMovingWindowNewFrame so we fill g.HoveredWindowUnderMovingWindow on the mouse
6468 // release frame) (currently needs to be done after the WasActive=Active loop and FindHoveredWindowEx uses ->Active)
6469 UpdateHoveredWindowAndCaptureFlags(g.IO.MousePos);
6470
6471 // Handle user moving window with mouse (at the beginning of the frame to avoid input lag or sheering)
6472 UpdateMouseMovingWindowNewFrame();
6473
6474 // Background darkening/whitening
6475 if (GetTopMostPopupModal() != NULL || (g.NavWindowingTarget != NULL && g.NavWindowingHighlightAlpha > 0.0f))
6476 g.DimBgRatio = ImMin(g.DimBgRatio + g.IO.DeltaTime * 6.0f, 1.0f);
6477 else
6478 g.DimBgRatio = ImMax(g.DimBgRatio - g.IO.DeltaTime * 10.0f, 0.0f);
6479
6480 g.MouseCursor = ImGuiMouseCursor_Arrow;
6481 g.WantCaptureMouseNextFrame = g.WantCaptureKeyboardNextFrame = g.WantTextInputNextFrame = -1;
6482
6483 // Platform IME data: reset for the frame
6484 g.PlatformImeDataPrev = g.PlatformImeData;
6485 g.PlatformImeData.WantVisible = false;
6486
6487 // Mouse wheel scrolling, scale
6488 UpdateMouseWheel();
6489
6490 // Garbage collect transient buffers of recently unused tables
6491 for (int i = 0; i < g.TablesLastTimeActive.Size; i++)
6492 if (g.TablesLastTimeActive[i] >= 0.0f && g.TablesLastTimeActive[i] < memory_compact_start_time)
6493 TableGcCompactTransientBuffers(g.Tables.GetByIndex(i));
6494 for (ImGuiTableTempData &table_temp_data : g.TablesTempData)
6495 if (table_temp_data.LastTimeActive >= 0.0f && table_temp_data.LastTimeActive < memory_compact_start_time)
6496 TableGcCompactTransientBuffers(&table_temp_data);
6497 if (g.GcCompactAll)
6498 GcCompactTransientMiscBuffers();
6499 g.GcCompactAll = false;
6500
6501 // Closing the focused window restore focus to the first active root window in descending z-order
6502 if (g.NavWindow && !g.NavWindow->WasActive)
6503 FocusTopMostWindowUnderOne(NULL, NULL, NULL, ImGuiFocusRequestFlags_RestoreFocusedChild);
6504
6505 // No window should be open at the beginning of the frame.
6506 // But in order to allow the user to call NewFrame() multiple times without calling Render(), we are doing an
6507 // explicit clear.
6508 g.CurrentWindowStack.resize(0);
6509 g.BeginPopupStack.resize(0);
6510 g.ItemFlagsStack.resize(0);
6511 g.ItemFlagsStack.push_back(ImGuiItemFlags_AutoClosePopups); // Default flags
6512 g.CurrentItemFlags = g.ItemFlagsStack.back();
6513 g.GroupStack.resize(0);
6514
6515 // Docking
6516 DockContextNewFrameUpdateDocking(&g);
6517
6518 // [DEBUG] Update debug features
6519#ifndef IMGUI_DISABLE_DEBUG_TOOLS
6520 UpdateDebugToolItemPicker();
6521 UpdateDebugToolStackQueries();
6522 UpdateDebugToolFlashStyleColor();
6523 if (g.DebugLocateFrames > 0 && --g.DebugLocateFrames == 0)
6524 {
6525 g.DebugLocateId = 0;
6526 g.DebugBreakInLocateId = false;
6527 }
6528 if (g.DebugLogAutoDisableFrames > 0 && --g.DebugLogAutoDisableFrames == 0)
6529 {
6530 DebugLog("(Debug Log: Auto-disabled some ImGuiDebugLogFlags after 2 frames)\n");
6531 g.DebugLogFlags &= ~g.DebugLogAutoDisableFlags;
6532 g.DebugLogAutoDisableFlags = ImGuiDebugLogFlags_None;
6533 }
6534#endif
6535
6536 // Create implicit/fallback window - which we will only render it if the user has added something to it.
6537 // We don't use "Debug" to avoid colliding with user trying to create a "Debug" window with custom flags.
6538 // This fallback is particularly important as it prevents ImGui:: calls from crashing.
6539 g.WithinFrameScopeWithImplicitWindow = true;
6540 SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver);
6541 Begin("Debug##Default");
6542 IM_ASSERT(g.CurrentWindow->IsFallbackWindow == true);
6543
6544 // Store stack sizes
6545 g.ErrorCountCurrentFrame = 0;
6546 ErrorRecoveryStoreState(&g.StackSizesInNewFrame);
6547
6548 // [DEBUG] When io.ConfigDebugBeginReturnValue is set, we make Begin()/BeginChild() return false at different level
6549 // of the window-stack, allowing to validate correct Begin/End behavior in user code.
6550#ifndef IMGUI_DISABLE_DEBUG_TOOLS
6551 if (g.IO.ConfigDebugBeginReturnValueLoop)
6552 g.DebugBeginReturnValueCullDepth =
6553 (g.DebugBeginReturnValueCullDepth == -1)
6554 ? 0
6555 : ((g.DebugBeginReturnValueCullDepth + ((g.FrameCount % 4) == 0 ? 1 : 0)) % 10);
6556 else
6557 g.DebugBeginReturnValueCullDepth = -1;
6558#endif
6559
6560 CallContextHooks(&g, ImGuiContextHookType_NewFramePost);
6561}
6562
6563// FIXME: Add a more explicit sort order in the window structure.
6564static int IMGUI_CDECL ChildWindowComparer(const void *lhs, const void *rhs)
6565{
6566 const ImGuiWindow *const a = *(const ImGuiWindow *const *)lhs;
6567 const ImGuiWindow *const b = *(const ImGuiWindow *const *)rhs;
6568 if (int d = (a->Flags & ImGuiWindowFlags_Popup) - (b->Flags & ImGuiWindowFlags_Popup))
6569 return d;
6570 if (int d = (a->Flags & ImGuiWindowFlags_Tooltip) - (b->Flags & ImGuiWindowFlags_Tooltip))
6571 return d;
6572 return (a->BeginOrderWithinParent - b->BeginOrderWithinParent);
6573}
6574
6575static void AddWindowToSortBuffer(ImVector<ImGuiWindow *> *out_sorted_windows, ImGuiWindow *window)
6576{
6577 out_sorted_windows->push_back(window);
6578 if (window->Active)
6579 {
6580 int count = window->DC.ChildWindows.Size;
6581 ImQsort(window->DC.ChildWindows.Data, (size_t)count, sizeof(ImGuiWindow *), ChildWindowComparer);
6582 for (int i = 0; i < count; i++)
6583 {
6584 ImGuiWindow *child = window->DC.ChildWindows[i];
6585 if (child->Active)
6586 AddWindowToSortBuffer(out_sorted_windows, child);
6587 }
6588 }
6589}
6590
6591static void AddWindowToDrawData(ImGuiWindow *window, int layer)
6592{
6593 ImGuiContext &g = *GImGui;
6594 ImGuiViewportP *viewport = window->Viewport;
6595 IM_ASSERT(viewport != NULL);
6596 g.IO.MetricsRenderWindows++;
6597 if (window->DrawList->_Splitter._Count > 1)
6598 window->DrawList->ChannelsMerge(); // Merge if user forgot to merge back. Also required in Docking branch for
6599 // ImGuiWindowFlags_DockNodeHost windows.
6600 ImGui::AddDrawListToDrawDataEx(&viewport->DrawDataP, viewport->DrawDataBuilder.Layers[layer], window->DrawList);
6601 for (ImGuiWindow *child : window->DC.ChildWindows)
6602 if (IsWindowActiveAndVisible(child)) // Clipped children may have been marked not active
6603 AddWindowToDrawData(child, layer);
6604}
6605
6606static inline int GetWindowDisplayLayer(ImGuiWindow *window)
6607{
6608 return (window->Flags & ImGuiWindowFlags_Tooltip) ? 1 : 0;
6609}
6610
6611// Layer is locked for the root window, however child windows may use a different viewport (e.g. extruding menu)
6612static inline void AddRootWindowToDrawData(ImGuiWindow *window)
6613{
6614 AddWindowToDrawData(window, GetWindowDisplayLayer(window));
6615}
6616
6617static void FlattenDrawDataIntoSingleLayer(ImDrawDataBuilder *builder)
6618{
6619 int n = builder->Layers[0]->Size;
6620 int full_size = n;
6621 for (int i = 1; i < IM_ARRAYSIZE(builder->Layers); i++)
6622 full_size += builder->Layers[i]->Size;
6623 builder->Layers[0]->resize(full_size);
6624 for (int layer_n = 1; layer_n < IM_ARRAYSIZE(builder->Layers); layer_n++)
6625 {
6626 ImVector<ImDrawList *> *layer = builder->Layers[layer_n];
6627 if (layer->empty())
6628 continue;
6629 memcpy(builder->Layers[0]->Data + n, layer->Data, layer->Size * sizeof(ImDrawList *));
6630 n += layer->Size;
6631 layer->resize(0);
6632 }
6633}
6634
6635static void InitViewportDrawData(ImGuiViewportP *viewport)
6636{
6637 ImGuiIO &io = ImGui::GetIO();
6638 ImDrawData *draw_data = &viewport->DrawDataP;
6639
6640 viewport->DrawData = draw_data; // Make publicly accessible
6641 viewport->DrawDataBuilder.Layers[0] = &draw_data->CmdLists;
6642 viewport->DrawDataBuilder.Layers[1] = &viewport->DrawDataBuilder.LayerData1;
6643 viewport->DrawDataBuilder.Layers[0]->resize(0);
6644 viewport->DrawDataBuilder.Layers[1]->resize(0);
6645
6646 // When minimized, we report draw_data->DisplaySize as zero to be consistent with non-viewport mode,
6647 // and to allow applications/backends to easily skip rendering.
6648 // FIXME: Note that we however do NOT attempt to report "zero drawlist / vertices" into the ImDrawData structure.
6649 // This is because the work has been done already, and its wasted! We should fix that and add optimizations for
6650 // it earlier in the pipeline, rather than pretend to hide the data at the end of the pipeline.
6651 const bool is_minimized = (viewport->Flags & ImGuiViewportFlags_IsMinimized) != 0;
6652
6653 draw_data->Valid = true;
6654 draw_data->CmdListsCount = 0;
6655 draw_data->TotalVtxCount = draw_data->TotalIdxCount = 0;
6656 draw_data->DisplayPos = viewport->Pos;
6657 draw_data->DisplaySize = is_minimized ? ImVec2(0.0f, 0.0f) : viewport->Size;
6658 draw_data->FramebufferScale =
6659 io.DisplayFramebufferScale; // FIXME-VIEWPORT: This may vary on a per-monitor/viewport basis?
6660 draw_data->OwnerViewport = viewport;
6661}
6662
6663// Push a clipping rectangle for both ImGui logic (hit-testing etc.) and low-level ImDrawList rendering.
6664// - When using this function it is sane to ensure that float are perfectly rounded to integer values,
6665// so that e.g. (int)(max.x-min.x) in user's render produce correct result.
6666// - If the code here changes, may need to update code of functions like NextColumn() and PushColumnClipRect():
6667// some frequently called functions which to modify both channels and clipping simultaneously tend to use the
6668// more specialized SetWindowClipRectBeforeSetChannel() to avoid extraneous updates of underlying ImDrawCmds.
6669// - This is analogous to PushFont()/PopFont() in the sense that are a mixing a global stack and a window stack,
6670// which in the case of ClipRect is not so problematic but tends to be more restrictive for fonts.
6671void ImGui::PushClipRect(const ImVec2 &clip_rect_min, const ImVec2 &clip_rect_max,
6672 bool intersect_with_current_clip_rect)
6673{
6674 ImGuiWindow *window = GetCurrentWindow();
6675 window->DrawList->PushClipRect(clip_rect_min, clip_rect_max, intersect_with_current_clip_rect);
6676 window->ClipRect = window->DrawList->_ClipRectStack.back();
6677}
6678
6679void ImGui::PopClipRect()
6680{
6681 ImGuiWindow *window = GetCurrentWindow();
6682 window->DrawList->PopClipRect();
6683 window->ClipRect = window->DrawList->_ClipRectStack.back();
6684}
6685
6686static ImGuiWindow *FindFrontMostVisibleChildWindow(ImGuiWindow *window)
6687{
6688 for (int n = window->DC.ChildWindows.Size - 1; n >= 0; n--)
6689 if (IsWindowActiveAndVisible(window->DC.ChildWindows[n]))
6690 return FindFrontMostVisibleChildWindow(window->DC.ChildWindows[n]);
6691 return window;
6692}
6693
6694static void ImGui::RenderDimmedBackgroundBehindWindow(ImGuiWindow *window, ImU32 col)
6695{
6696 if ((col & IM_COL32_A_MASK) == 0)
6697 return;
6698
6699 ImGuiViewportP *viewport = window->Viewport;
6700 ImRect viewport_rect = viewport->GetMainRect();
6701
6702 // Draw behind window by moving the draw command at the FRONT of the draw list
6703 {
6704 // Draw list have been trimmed already, hence the explicit recreation of a draw command if missing.
6705 // FIXME: This is creating complication, might be simpler if we could inject a drawlist in drawdata at a given
6706 // position and not attempt to manipulate ImDrawCmd order.
6707 ImDrawList *draw_list = window->RootWindowDockTree->DrawList;
6708 draw_list->ChannelsMerge();
6709 if (draw_list->CmdBuffer.Size == 0)
6710 draw_list->AddDrawCmd();
6711 draw_list->PushClipRect(viewport_rect.Min - ImVec2(1, 1), viewport_rect.Max + ImVec2(1, 1),
6712 false); // FIXME: Need to stricty ensure ImDrawCmd are not merged (ElemCount==6 checks
6713 // below will verify that)
6714 draw_list->AddRectFilled(viewport_rect.Min, viewport_rect.Max, col);
6715 ImDrawCmd cmd = draw_list->CmdBuffer.back();
6716 IM_ASSERT(cmd.ElemCount == 6);
6717 draw_list->CmdBuffer.pop_back();
6718 draw_list->CmdBuffer.push_front(cmd);
6719 draw_list->AddDrawCmd(); // We need to create a command as CmdBuffer.back().IdxOffset won't be correct if we
6720 // append to same command.
6721 draw_list->PopClipRect();
6722 }
6723
6724 // Draw over sibling docking nodes in a same docking tree
6725 if (window->RootWindow->DockIsActive)
6726 {
6727 ImDrawList *draw_list = FindFrontMostVisibleChildWindow(window->RootWindowDockTree)->DrawList;
6728 draw_list->ChannelsMerge();
6729 if (draw_list->CmdBuffer.Size == 0)
6730 draw_list->AddDrawCmd();
6731 draw_list->PushClipRect(viewport_rect.Min, viewport_rect.Max, false);
6732 RenderRectFilledWithHole(draw_list, window->RootWindowDockTree->Rect(), window->RootWindow->Rect(), col,
6733 0.0f); // window->RootWindowDockTree->WindowRounding);
6734 draw_list->PopClipRect();
6735 }
6736}
6737
6738ImGuiWindow *ImGui::FindBottomMostVisibleWindowWithinBeginStack(ImGuiWindow *parent_window)
6739{
6740 ImGuiContext &g = *GImGui;
6741 ImGuiWindow *bottom_most_visible_window = parent_window;
6742 for (int i = FindWindowDisplayIndex(parent_window); i >= 0; i--)
6743 {
6744 ImGuiWindow *window = g.Windows[i];
6745 if (window->Flags & ImGuiWindowFlags_ChildWindow)
6746 continue;
6747 if (!IsWindowWithinBeginStackOf(window, parent_window))
6748 break;
6749 if (IsWindowActiveAndVisible(window) && GetWindowDisplayLayer(window) <= GetWindowDisplayLayer(parent_window))
6750 bottom_most_visible_window = window;
6751 }
6752 return bottom_most_visible_window;
6753}
6754
6755// Important: AddWindowToDrawData() has not been called yet, meaning DockNodeHost windows needs a
6756// DrawList->ChannelsMerge() before usage. We call ChannelsMerge() lazily here at it is faster that doing a full
6757// iteration of g.Windows[] prior to calling RenderDimmedBackgrounds().
6758static void ImGui::RenderDimmedBackgrounds()
6759{
6760 ImGuiContext &g = *GImGui;
6761 ImGuiWindow *modal_window = GetTopMostAndVisiblePopupModal();
6762 if (g.DimBgRatio <= 0.0f && g.NavWindowingHighlightAlpha <= 0.0f)
6763 return;
6764 const bool dim_bg_for_modal = (modal_window != NULL);
6765 const bool dim_bg_for_window_list = (g.NavWindowingTargetAnim != NULL && g.NavWindowingTargetAnim->Active);
6766 if (!dim_bg_for_modal && !dim_bg_for_window_list)
6767 return;
6768
6769 ImGuiViewport *viewports_already_dimmed[2] = {NULL, NULL};
6770 if (dim_bg_for_modal)
6771 {
6772 // Draw dimming behind modal or a begin stack child, whichever comes first in draw order.
6773 ImGuiWindow *dim_behind_window = FindBottomMostVisibleWindowWithinBeginStack(modal_window);
6774 RenderDimmedBackgroundBehindWindow(dim_behind_window,
6775 GetColorU32(modal_window->DC.ModalDimBgColor, g.DimBgRatio));
6776 viewports_already_dimmed[0] = modal_window->Viewport;
6777 }
6778 else if (dim_bg_for_window_list)
6779 {
6780 // Draw dimming behind CTRL+Tab target window and behind CTRL+Tab UI window
6781 RenderDimmedBackgroundBehindWindow(g.NavWindowingTargetAnim,
6782 GetColorU32(ImGuiCol_NavWindowingDimBg, g.DimBgRatio));
6783 if (g.NavWindowingListWindow != NULL && g.NavWindowingListWindow->Viewport &&
6784 g.NavWindowingListWindow->Viewport != g.NavWindowingTargetAnim->Viewport)
6785 RenderDimmedBackgroundBehindWindow(g.NavWindowingListWindow,
6786 GetColorU32(ImGuiCol_NavWindowingDimBg, g.DimBgRatio));
6787 viewports_already_dimmed[0] = g.NavWindowingTargetAnim->Viewport;
6788 viewports_already_dimmed[1] = g.NavWindowingListWindow ? g.NavWindowingListWindow->Viewport : NULL;
6789
6790 // Draw border around CTRL+Tab target window
6791 ImGuiWindow *window = g.NavWindowingTargetAnim;
6792 ImGuiViewport *viewport = window->Viewport;
6793 float distance = g.FontSize;
6794 ImRect bb = window->Rect();
6795 bb.Expand(distance);
6796 if (bb.GetWidth() >= viewport->Size.x && bb.GetHeight() >= viewport->Size.y)
6797 bb.Expand(-distance - 1.0f); // If a window fits the entire viewport, adjust its highlight inward
6798 window->DrawList->ChannelsMerge();
6799 if (window->DrawList->CmdBuffer.Size == 0)
6800 window->DrawList->AddDrawCmd();
6801 window->DrawList->PushClipRect(viewport->Pos, viewport->Pos + viewport->Size);
6802 window->DrawList->AddRect(bb.Min, bb.Max,
6803 GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha),
6804 window->WindowRounding, 0, 3.0f); // FIXME-DPI
6805 window->DrawList->PopClipRect();
6806 }
6807
6808 // Draw dimming background on _other_ viewports than the ones our windows are in
6809 for (ImGuiViewportP *viewport : g.Viewports)
6810 {
6811 if (viewport == viewports_already_dimmed[0] || viewport == viewports_already_dimmed[1])
6812 continue;
6813 if (modal_window && viewport->Window && IsWindowAbove(viewport->Window, modal_window))
6814 continue;
6815 ImDrawList *draw_list = GetForegroundDrawList(viewport);
6816 const ImU32 dim_bg_col =
6817 GetColorU32(dim_bg_for_modal ? ImGuiCol_ModalWindowDimBg : ImGuiCol_NavWindowingDimBg, g.DimBgRatio);
6818 draw_list->AddRectFilled(viewport->Pos, viewport->Pos + viewport->Size, dim_bg_col);
6819 }
6820}
6821
6822// This is normally called by Render(). You may want to call it directly if you want to avoid calling Render() but the
6823// gain will be very minimal.
6824void ImGui::EndFrame()
6825{
6826 ImGuiContext &g = *GImGui;
6827 IM_ASSERT(g.Initialized);
6828
6829 // Don't process EndFrame() multiple times.
6830 if (g.FrameCountEnded == g.FrameCount)
6831 return;
6832 IM_ASSERT(g.WithinFrameScope && "Forgot to call ImGui::NewFrame()?");
6833
6834 CallContextHooks(&g, ImGuiContextHookType_EndFramePre);
6835
6836 // [EXPERIMENTAL] Recover from errors
6837 if (g.IO.ConfigErrorRecovery)
6838 ErrorRecoveryTryToRecoverState(&g.StackSizesInNewFrame);
6839 ErrorCheckEndFrameSanityChecks();
6840 ErrorCheckEndFrameFinalizeErrorTooltip();
6841
6842 // Notify Platform/OS when our Input Method Editor cursor has moved (e.g. CJK inputs using Microsoft IME)
6843 ImGuiPlatformImeData *ime_data = &g.PlatformImeData;
6844 if (g.PlatformIO.Platform_SetImeDataFn != NULL &&
6845 memcmp(ime_data, &g.PlatformImeDataPrev, sizeof(ImGuiPlatformImeData)) != 0)
6846 {
6847 ImGuiViewport *viewport = FindViewportByID(g.PlatformImeViewport);
6848 IMGUI_DEBUG_LOG_IO("[io] Calling Platform_SetImeDataFn(): WantVisible: %d, InputPos (%.2f,%.2f)\n",
6849 ime_data->WantVisible, ime_data->InputPos.x, ime_data->InputPos.y);
6850 if (viewport == NULL)
6851 viewport = GetMainViewport();
6852 g.PlatformIO.Platform_SetImeDataFn(&g, viewport, ime_data);
6853 }
6854
6855 // Hide implicit/fallback "Debug" window if it hasn't been used
6856 g.WithinFrameScopeWithImplicitWindow = false;
6857 if (g.CurrentWindow && !g.CurrentWindow->WriteAccessed)
6858 g.CurrentWindow->Active = false;
6859 End();
6860
6861 // Update navigation: CTRL+Tab, wrap-around requests
6862 NavEndFrame();
6863
6864 // Update docking
6865 DockContextEndFrame(&g);
6866
6867 SetCurrentViewport(NULL, NULL);
6868
6869 // Drag and Drop: Elapse payload (if delivered, or if source stops being submitted)
6870 if (g.DragDropActive)
6871 {
6872 bool is_delivered = g.DragDropPayload.Delivery;
6873 bool is_elapsed = (g.DragDropSourceFrameCount + 1 < g.FrameCount) &&
6874 ((g.DragDropSourceFlags & ImGuiDragDropFlags_PayloadAutoExpire) ||
6875 g.DragDropMouseButton == -1 || !IsMouseDown(g.DragDropMouseButton));
6876 if (is_delivered || is_elapsed)
6877 ClearDragDrop();
6878 }
6879
6880 // Drag and Drop: Fallback for missing source tooltip. This is not ideal but better than nothing.
6881 // If you want to handle source item disappearing: instead of submitting your description tooltip
6882 // in the BeginDragDropSource() block of the dragged item, you can submit them from a safe single spot
6883 // (e.g. end of your item loop, or before EndFrame) by reading payload data.
6884 // In the typical case, the contents of drag tooltip should be possible to infer solely from payload data.
6885 if (g.DragDropActive && g.DragDropSourceFrameCount + 1 < g.FrameCount &&
6886 !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoPreviewTooltip))
6887 {
6888 g.DragDropWithinSource = true;
6889 SetTooltip("...");
6890 g.DragDropWithinSource = false;
6891 }
6892
6893 // End frame
6894 g.WithinFrameScope = false;
6895 g.FrameCountEnded = g.FrameCount;
6896
6897 // Initiate moving window + handle left-click and right-click focus
6898 UpdateMouseMovingWindowEndFrame();
6899
6900 // Update user-facing viewport list (g.Viewports -> g.PlatformIO.Viewports after filtering out some)
6901 UpdateViewportsEndFrame();
6902
6903 // Sort the window list so that all child windows are after their parent
6904 // We cannot do that on FocusWindow() because children may not exist yet
6905 g.WindowsTempSortBuffer.resize(0);
6906 g.WindowsTempSortBuffer.reserve(g.Windows.Size);
6907 for (ImGuiWindow *window : g.Windows)
6908 {
6909 if (window->Active &&
6910 (window->Flags & ImGuiWindowFlags_ChildWindow)) // if a child is active its parent will add it
6911 continue;
6912 AddWindowToSortBuffer(&g.WindowsTempSortBuffer, window);
6913 }
6914
6915 // This usually assert if there is a mismatch between the ImGuiWindowFlags_ChildWindow / ParentWindow values and
6916 // DC.ChildWindows[] in parents, aka we've done something wrong.
6917 IM_ASSERT(g.Windows.Size == g.WindowsTempSortBuffer.Size);
6918 g.Windows.swap(g.WindowsTempSortBuffer);
6919 g.IO.MetricsActiveWindows = g.WindowsActiveCount;
6920
6921 // Unlock font atlas
6922 g.IO.Fonts->Locked = false;
6923
6924 // Clear Input data for next frame
6925 g.IO.MousePosPrev = g.IO.MousePos;
6926 g.IO.AppFocusLost = false;
6927 g.IO.MouseWheel = g.IO.MouseWheelH = 0.0f;
6928 g.IO.InputQueueCharacters.resize(0);
6929
6930 CallContextHooks(&g, ImGuiContextHookType_EndFramePost);
6931}
6932
6933// Prepare the data for rendering so you can call GetDrawData()
6934// (As with anything within the ImGui:: namespace this doesn't touch your GPU or graphics API at all:
6935// it is the role of the ImGui_ImplXXXX_RenderDrawData() function provided by the renderer backend)
6936void ImGui::Render()
6937{
6938 ImGuiContext &g = *GImGui;
6939 IM_ASSERT(g.Initialized);
6940
6941 if (g.FrameCountEnded != g.FrameCount)
6942 EndFrame();
6943 if (g.FrameCountRendered == g.FrameCount)
6944 return;
6945 g.FrameCountRendered = g.FrameCount;
6946
6947 g.IO.MetricsRenderWindows = 0;
6948 CallContextHooks(&g, ImGuiContextHookType_RenderPre);
6949
6950 // Add background ImDrawList (for each active viewport)
6951 for (ImGuiViewportP *viewport : g.Viewports)
6952 {
6953 InitViewportDrawData(viewport);
6954 if (viewport->BgFgDrawLists[0] != NULL)
6955 AddDrawListToDrawDataEx(&viewport->DrawDataP, viewport->DrawDataBuilder.Layers[0],
6956 GetBackgroundDrawList(viewport));
6957 }
6958
6959 // Draw modal/window whitening backgrounds
6960 RenderDimmedBackgrounds();
6961
6962 // Add ImDrawList to render
6963 ImGuiWindow *windows_to_render_top_most[2];
6964 windows_to_render_top_most[0] =
6965 (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus))
6966 ? g.NavWindowingTarget->RootWindowDockTree
6967 : NULL;
6968 windows_to_render_top_most[1] = (g.NavWindowingTarget ? g.NavWindowingListWindow : NULL);
6969 for (ImGuiWindow *window : g.Windows)
6970 {
6971 IM_MSVC_WARNING_SUPPRESS(
6972 6011); // Static Analysis false positive "warning C6011: Dereferencing NULL pointer 'window'"
6973 if (IsWindowActiveAndVisible(window) && (window->Flags & ImGuiWindowFlags_ChildWindow) == 0 &&
6974 window != windows_to_render_top_most[0] && window != windows_to_render_top_most[1])
6975 AddRootWindowToDrawData(window);
6976 }
6977 for (int n = 0; n < IM_ARRAYSIZE(windows_to_render_top_most); n++)
6978 if (windows_to_render_top_most[n] &&
6979 IsWindowActiveAndVisible(windows_to_render_top_most[n])) // NavWindowingTarget is always temporarily
6980 // displayed as the top-most window
6981 AddRootWindowToDrawData(windows_to_render_top_most[n]);
6982
6983 // Draw software mouse cursor if requested by io.MouseDrawCursor flag
6984 if (g.IO.MouseDrawCursor && g.MouseCursor != ImGuiMouseCursor_None)
6985 RenderMouseCursor(g.IO.MousePos, g.Style.MouseCursorScale, g.MouseCursor, IM_COL32_WHITE, IM_COL32_BLACK,
6986 IM_COL32(0, 0, 0, 48));
6987
6988 // Setup ImDrawData structures for end-user
6989 g.IO.MetricsRenderVertices = g.IO.MetricsRenderIndices = 0;
6990 for (ImGuiViewportP *viewport : g.Viewports)
6991 {
6992 FlattenDrawDataIntoSingleLayer(&viewport->DrawDataBuilder);
6993
6994 // Add foreground ImDrawList (for each active viewport)
6995 if (viewport->BgFgDrawLists[1] != NULL)
6996 AddDrawListToDrawDataEx(&viewport->DrawDataP, viewport->DrawDataBuilder.Layers[0],
6997 GetForegroundDrawList(viewport));
6998
6999 // We call _PopUnusedDrawCmd() last thing, as RenderDimmedBackgrounds() rely on a valid command being there
7000 // (especially in docking branch).
7001 ImDrawData *draw_data = &viewport->DrawDataP;
7002 IM_ASSERT(draw_data->CmdLists.Size == draw_data->CmdListsCount);
7003 for (ImDrawList *draw_list : draw_data->CmdLists)
7004 draw_list->_PopUnusedDrawCmd();
7005
7006 g.IO.MetricsRenderVertices += draw_data->TotalVtxCount;
7007 g.IO.MetricsRenderIndices += draw_data->TotalIdxCount;
7008 }
7009
7010 CallContextHooks(&g, ImGuiContextHookType_RenderPost);
7011}
7012
7013// Calculate text size. Text can be multi-line. Optionally ignore text after a ## marker.
7014// CalcTextSize("") should return ImVec2(0.0f, g.FontSize)
7015ImVec2 ImGui::CalcTextSize(const char *text, const char *text_end, bool hide_text_after_double_hash, float wrap_width)
7016{
7017 ImGuiContext &g = *GImGui;
7018
7019 const char *text_display_end;
7020 if (hide_text_after_double_hash)
7021 text_display_end = FindRenderedTextEnd(text, text_end); // Hide anything after a '##' string
7022 else
7023 text_display_end = text_end;
7024
7025 ImFont *font = g.Font;
7026 const float font_size = g.FontSize;
7027 if (text == text_display_end)
7028 return ImVec2(0.0f, font_size);
7029 ImVec2 text_size = font->CalcTextSizeA(font_size, FLT_MAX, wrap_width, text, text_display_end, NULL);
7030
7031 // Round
7032 // FIXME: This has been here since Dec 2015 (7b0bf230) but down the line we want this out.
7033 // FIXME: Investigate using ceilf or e.g.
7034 // - https://git.musl-libc.org/cgit/musl/tree/src/math/ceilf.c
7035 // - https://embarkstudios.github.io/rust-gpu/api/src/libm/math/ceilf.rs.html
7036 text_size.x = IM_TRUNC(text_size.x + 0.99999f);
7037
7038 return text_size;
7039}
7040
7041// Find window given position, search front-to-back
7042// - Typically write output back to g.HoveredWindow and g.HoveredWindowUnderMovingWindow.
7043// - FIXME: Note that we have an inconsequential lag here: OuterRectClipped is updated in Begin(), so windows moved
7044// programmatically
7045// with SetWindowPos() and not SetNextWindowPos() will have that rectangle lagging by a frame at the time
7046// FindHoveredWindow() is called, aka before the next Begin(). Moving window isn't affected.
7047// - The 'find_first_and_in_any_viewport = true' mode is only used by TestEngine. It is simpler to maintain here.
7048void ImGui::FindHoveredWindowEx(const ImVec2 &pos, bool find_first_and_in_any_viewport,
7049 ImGuiWindow **out_hovered_window, ImGuiWindow **out_hovered_window_under_moving_window)
7050{
7051 ImGuiContext &g = *GImGui;
7052 ImGuiWindow *hovered_window = NULL;
7053 ImGuiWindow *hovered_window_under_moving_window = NULL;
7054
7055 // Special handling for the window being moved: Ignore the mouse viewport check (because it may reset/lose its
7056 // viewport during the undocking frame)
7057 ImGuiViewportP *backup_moving_window_viewport = NULL;
7058 if (find_first_and_in_any_viewport == false && g.MovingWindow)
7059 {
7060 backup_moving_window_viewport = g.MovingWindow->Viewport;
7061 g.MovingWindow->Viewport = g.MouseViewport;
7062 if (!(g.MovingWindow->Flags & ImGuiWindowFlags_NoMouseInputs))
7063 hovered_window = g.MovingWindow;
7064 }
7065
7066 ImVec2 padding_regular = g.Style.TouchExtraPadding;
7067 ImVec2 padding_for_resize =
7068 ImMax(g.Style.TouchExtraPadding, ImVec2(g.Style.WindowBorderHoverPadding, g.Style.WindowBorderHoverPadding));
7069 for (int i = g.Windows.Size - 1; i >= 0; i--)
7070 {
7071 ImGuiWindow *window = g.Windows[i];
7072 IM_MSVC_WARNING_SUPPRESS(28182); // [Static Analyzer] Dereferencing NULL pointer.
7073 if (!window->WasActive || window->Hidden)
7074 continue;
7075 if (window->Flags & ImGuiWindowFlags_NoMouseInputs)
7076 continue;
7077 IM_ASSERT(window->Viewport);
7078 if (window->Viewport != g.MouseViewport)
7079 continue;
7080
7081 // Using the clipped AABB, a child window will typically be clipped by its parent (not always)
7082 ImVec2 hit_padding = (window->Flags & (ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize))
7083 ? padding_regular
7084 : padding_for_resize;
7085 if (!window->OuterRectClipped.ContainsWithPad(pos, hit_padding))
7086 continue;
7087
7088 // Support for one rectangular hole in any given window
7089 // FIXME: Consider generalizing hit-testing override (with more generic data, callback, etc.) (#1512)
7090 if (window->HitTestHoleSize.x != 0)
7091 {
7092 ImVec2 hole_pos(window->Pos.x + (float)window->HitTestHoleOffset.x,
7093 window->Pos.y + (float)window->HitTestHoleOffset.y);
7094 ImVec2 hole_size((float)window->HitTestHoleSize.x, (float)window->HitTestHoleSize.y);
7095 if (ImRect(hole_pos, hole_pos + hole_size).Contains(pos))
7096 continue;
7097 }
7098
7099 if (find_first_and_in_any_viewport)
7100 {
7101 hovered_window = window;
7102 break;
7103 }
7104 else
7105 {
7106 if (hovered_window == NULL)
7107 hovered_window = window;
7108 IM_MSVC_WARNING_SUPPRESS(28182); // [Static Analyzer] Dereferencing NULL pointer.
7109 if (hovered_window_under_moving_window == NULL &&
7110 (!g.MovingWindow || window->RootWindowDockTree != g.MovingWindow->RootWindowDockTree))
7111 hovered_window_under_moving_window = window;
7112 if (hovered_window && hovered_window_under_moving_window)
7113 break;
7114 }
7115 }
7116
7117 *out_hovered_window = hovered_window;
7118 if (out_hovered_window_under_moving_window != NULL)
7119 *out_hovered_window_under_moving_window = hovered_window_under_moving_window;
7120 if (find_first_and_in_any_viewport == false && g.MovingWindow)
7121 g.MovingWindow->Viewport = backup_moving_window_viewport;
7122}
7123
7124bool ImGui::IsItemActive()
7125{
7126 ImGuiContext &g = *GImGui;
7127 if (g.ActiveId)
7128 return g.ActiveId == g.LastItemData.ID;
7129 return false;
7130}
7131
7132bool ImGui::IsItemActivated()
7133{
7134 ImGuiContext &g = *GImGui;
7135 if (g.ActiveId)
7136 if (g.ActiveId == g.LastItemData.ID && g.ActiveIdPreviousFrame != g.LastItemData.ID)
7137 return true;
7138 return false;
7139}
7140
7141bool ImGui::IsItemDeactivated()
7142{
7143 ImGuiContext &g = *GImGui;
7144 if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasDeactivated)
7145 return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Deactivated) != 0;
7146 return (g.DeactivatedItemData.ID == g.LastItemData.ID && g.LastItemData.ID != 0 &&
7147 g.DeactivatedItemData.ElapseFrame >= g.FrameCount);
7148}
7149
7150bool ImGui::IsItemDeactivatedAfterEdit()
7151{
7152 ImGuiContext &g = *GImGui;
7153 return IsItemDeactivated() && g.DeactivatedItemData.HasBeenEditedBefore;
7154}
7155
7156// == (GetItemID() == GetFocusID() && GetFocusID() != 0)
7157bool ImGui::IsItemFocused()
7158{
7159 ImGuiContext &g = *GImGui;
7160 if (g.NavId != g.LastItemData.ID || g.NavId == 0)
7161 return false;
7162
7163 // Special handling for the dummy item after Begin() which represent the title bar or tab.
7164 // When the window is collapsed (SkipItems==true) that last item will never be overwritten so we need to detect the
7165 // case.
7166 ImGuiWindow *window = g.CurrentWindow;
7167 if (g.LastItemData.ID == window->ID && window->WriteAccessed)
7168 return false;
7169
7170 return true;
7171}
7172
7173// Important: this can be useful but it is NOT equivalent to the behavior of e.g.Button()!
7174// Most widgets have specific reactions based on mouse-up/down state, mouse position etc.
7175bool ImGui::IsItemClicked(ImGuiMouseButton mouse_button)
7176{
7177 return IsMouseClicked(mouse_button) && IsItemHovered(ImGuiHoveredFlags_None);
7178}
7179
7180bool ImGui::IsItemToggledOpen()
7181{
7182 ImGuiContext &g = *GImGui;
7183 return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_ToggledOpen) ? true : false;
7184}
7185
7186// Call after a Selectable() or TreeNode() involved in multi-selection.
7187// Useful if you need the per-item information before reaching EndMultiSelect(), e.g. for rendering purpose.
7188// This is only meant to be called inside a BeginMultiSelect()/EndMultiSelect() block.
7189// (Outside of multi-select, it would be misleading/ambiguous to report this signal, as widgets
7190// return e.g. a pressed event and user code is in charge of altering selection in ways we cannot predict.)
7191bool ImGui::IsItemToggledSelection()
7192{
7193 ImGuiContext &g = *GImGui;
7194 IM_ASSERT(g.CurrentMultiSelect != NULL); // Can only be used inside a BeginMultiSelect()/EndMultiSelect()
7195 return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_ToggledSelection) ? true : false;
7196}
7197
7198// IMPORTANT: If you are trying to check whether your mouse should be dispatched to Dear ImGui or to your underlying
7199// app, you should not use this function! Use the 'io.WantCaptureMouse' boolean for that! Refer to FAQ entry "How can I
7200// tell whether to dispatch mouse/keyboard to Dear ImGui or my application?" for details.
7201bool ImGui::IsAnyItemHovered()
7202{
7203 ImGuiContext &g = *GImGui;
7204 return g.HoveredId != 0 || g.HoveredIdPreviousFrame != 0;
7205}
7206
7207bool ImGui::IsAnyItemActive()
7208{
7209 ImGuiContext &g = *GImGui;
7210 return g.ActiveId != 0;
7211}
7212
7213bool ImGui::IsAnyItemFocused()
7214{
7215 ImGuiContext &g = *GImGui;
7216 return g.NavId != 0 && g.NavCursorVisible;
7217}
7218
7219bool ImGui::IsItemVisible()
7220{
7221 ImGuiContext &g = *GImGui;
7222 return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible) != 0;
7223}
7224
7225bool ImGui::IsItemEdited()
7226{
7227 ImGuiContext &g = *GImGui;
7228 return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Edited) != 0;
7229}
7230
7231// Allow next item to be overlapped by subsequent items.
7232// This works by requiring HoveredId to match for two subsequent frames,
7233// so if a following items overwrite it our interactions will naturally be disabled.
7234void ImGui::SetNextItemAllowOverlap()
7235{
7236 ImGuiContext &g = *GImGui;
7237 g.NextItemData.ItemFlags |= ImGuiItemFlags_AllowOverlap;
7238}
7239
7240#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
7241// Allow last item to be overlapped by a subsequent item. Both may be activated during the same frame before the later
7242// one takes priority.
7243// FIXME-LEGACY: Use SetNextItemAllowOverlap() *before* your item instead.
7244void ImGui::SetItemAllowOverlap()
7245{
7246 ImGuiContext &g = *GImGui;
7247 ImGuiID id = g.LastItemData.ID;
7248 if (g.HoveredId == id)
7249 g.HoveredIdAllowOverlap = true;
7250 if (g.ActiveId == id) // Before we made this obsolete, most calls to SetItemAllowOverlap() used to avoid this path
7251 // by testing g.ActiveId != id.
7252 g.ActiveIdAllowOverlap = true;
7253}
7254#endif
7255
7256// This is a shortcut for not taking ownership of 100+ keys, frequently used by drag operations.
7257// FIXME: It might be undesirable that this will likely disable KeyOwner-aware shortcuts systems. Consider a more
7258// fine-tuned version if needed?
7259void ImGui::SetActiveIdUsingAllKeyboardKeys()
7260{
7261 ImGuiContext &g = *GImGui;
7262 IM_ASSERT(g.ActiveId != 0);
7263 g.ActiveIdUsingNavDirMask = (1 << ImGuiDir_COUNT) - 1;
7264 g.ActiveIdUsingAllKeyboardKeys = true;
7265 NavMoveRequestCancel();
7266}
7267
7268ImGuiID ImGui::GetItemID()
7269{
7270 ImGuiContext &g = *GImGui;
7271 return g.LastItemData.ID;
7272}
7273
7274ImVec2 ImGui::GetItemRectMin()
7275{
7276 ImGuiContext &g = *GImGui;
7277 return g.LastItemData.Rect.Min;
7278}
7279
7280ImVec2 ImGui::GetItemRectMax()
7281{
7282 ImGuiContext &g = *GImGui;
7283 return g.LastItemData.Rect.Max;
7284}
7285
7286ImVec2 ImGui::GetItemRectSize()
7287{
7288 ImGuiContext &g = *GImGui;
7289 return g.LastItemData.Rect.GetSize();
7290}
7291
7292// Prior to v1.90 2023/10/16, the BeginChild() function took a 'bool border = false' parameter instead of
7293// 'ImGuiChildFlags child_flags = 0'. ImGuiChildFlags_Borders is defined as always == 1 in order to allow old code
7294// passing 'true'. Read comments in imgui.h for details!
7295bool ImGui::BeginChild(const char *str_id, const ImVec2 &size_arg, ImGuiChildFlags child_flags,
7296 ImGuiWindowFlags window_flags)
7297{
7298 ImGuiID id = GetCurrentWindow()->GetID(str_id);
7299 return BeginChildEx(str_id, id, size_arg, child_flags, window_flags);
7300}
7301
7302bool ImGui::BeginChild(ImGuiID id, const ImVec2 &size_arg, ImGuiChildFlags child_flags, ImGuiWindowFlags window_flags)
7303{
7304 return BeginChildEx(NULL, id, size_arg, child_flags, window_flags);
7305}
7306
7307bool ImGui::BeginChildEx(const char *name, ImGuiID id, const ImVec2 &size_arg, ImGuiChildFlags child_flags,
7308 ImGuiWindowFlags window_flags)
7309{
7310 ImGuiContext &g = *GImGui;
7311 ImGuiWindow *parent_window = g.CurrentWindow;
7312 IM_ASSERT(id != 0);
7313
7314 // Sanity check as it is likely that some user will accidentally pass ImGuiWindowFlags into the ImGuiChildFlags
7315 // argument.
7316 const ImGuiChildFlags ImGuiChildFlags_SupportedMask_ =
7317 ImGuiChildFlags_Borders | ImGuiChildFlags_AlwaysUseWindowPadding | ImGuiChildFlags_ResizeX |
7318 ImGuiChildFlags_ResizeY | ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY |
7319 ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_FrameStyle | ImGuiChildFlags_NavFlattened;
7320 IM_UNUSED(ImGuiChildFlags_SupportedMask_);
7321 IM_ASSERT((child_flags & ~ImGuiChildFlags_SupportedMask_) == 0 &&
7322 "Illegal ImGuiChildFlags value. Did you pass ImGuiWindowFlags values instead of ImGuiChildFlags?");
7323 IM_ASSERT(
7324 (window_flags & ImGuiWindowFlags_AlwaysAutoResize) == 0 &&
7325 "Cannot specify ImGuiWindowFlags_AlwaysAutoResize for BeginChild(). Use ImGuiChildFlags_AlwaysAutoResize!");
7326 if (child_flags & ImGuiChildFlags_AlwaysAutoResize)
7327 {
7328 IM_ASSERT(
7329 (child_flags & (ImGuiChildFlags_ResizeX | ImGuiChildFlags_ResizeY)) == 0 &&
7330 "Cannot use ImGuiChildFlags_ResizeX or ImGuiChildFlags_ResizeY with ImGuiChildFlags_AlwaysAutoResize!");
7331 IM_ASSERT((child_flags & (ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY)) != 0 &&
7332 "Must use ImGuiChildFlags_AutoResizeX or ImGuiChildFlags_AutoResizeY with "
7333 "ImGuiChildFlags_AlwaysAutoResize!");
7334 }
7335#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
7336 if (window_flags & ImGuiWindowFlags_AlwaysUseWindowPadding)
7337 child_flags |= ImGuiChildFlags_AlwaysUseWindowPadding;
7338 if (window_flags & ImGuiWindowFlags_NavFlattened)
7339 child_flags |= ImGuiChildFlags_NavFlattened;
7340#endif
7341 if (child_flags & ImGuiChildFlags_AutoResizeX)
7342 child_flags &= ~ImGuiChildFlags_ResizeX;
7343 if (child_flags & ImGuiChildFlags_AutoResizeY)
7344 child_flags &= ~ImGuiChildFlags_ResizeY;
7345
7346 // Set window flags
7347 window_flags |= ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoDocking;
7348 window_flags |= (parent_window->Flags & ImGuiWindowFlags_NoMove); // Inherit the NoMove flag
7349 if (child_flags & (ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize))
7350 window_flags |= ImGuiWindowFlags_AlwaysAutoResize;
7351 if ((child_flags & (ImGuiChildFlags_ResizeX | ImGuiChildFlags_ResizeY)) == 0)
7352 window_flags |= ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings;
7353
7354 // Special framed style
7355 if (child_flags & ImGuiChildFlags_FrameStyle)
7356 {
7357 PushStyleColor(ImGuiCol_ChildBg, g.Style.Colors[ImGuiCol_FrameBg]);
7358 PushStyleVar(ImGuiStyleVar_ChildRounding, g.Style.FrameRounding);
7359 PushStyleVar(ImGuiStyleVar_ChildBorderSize, g.Style.FrameBorderSize);
7360 PushStyleVar(ImGuiStyleVar_WindowPadding, g.Style.FramePadding);
7361 child_flags |= ImGuiChildFlags_Borders | ImGuiChildFlags_AlwaysUseWindowPadding;
7362 window_flags |= ImGuiWindowFlags_NoMove;
7363 }
7364
7365 // Forward size
7366 // Important: Begin() has special processing to switch condition to ImGuiCond_FirstUseEver for a given axis when
7367 // ImGuiChildFlags_ResizeXXX is set. (the alternative would to store conditional flags per axis, which is possible
7368 // but more code)
7369 const ImVec2 size_avail = GetContentRegionAvail();
7370 const ImVec2 size_default((child_flags & ImGuiChildFlags_AutoResizeX) ? 0.0f : size_avail.x,
7371 (child_flags & ImGuiChildFlags_AutoResizeY) ? 0.0f : size_avail.y);
7372 ImVec2 size = CalcItemSize(size_arg, size_default.x, size_default.y);
7373
7374 // A SetNextWindowSize() call always has priority (#8020)
7375 // (since the code in Begin() never supported SizeVal==0.0f aka auto-resize via SetNextWindowSize() call, we don't
7376 // support it here for now)
7377 // FIXME: We only support ImGuiCond_Always in this path. Supporting other paths would requires to obtain window
7378 // pointer.
7379 if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSize) != 0 &&
7380 (g.NextWindowData.SizeCond & ImGuiCond_Always) != 0)
7381 {
7382 if (g.NextWindowData.SizeVal.x > 0.0f)
7383 {
7384 size.x = g.NextWindowData.SizeVal.x;
7385 child_flags &= ~ImGuiChildFlags_ResizeX;
7386 }
7387 if (g.NextWindowData.SizeVal.y > 0.0f)
7388 {
7389 size.y = g.NextWindowData.SizeVal.y;
7390 child_flags &= ~ImGuiChildFlags_ResizeY;
7391 }
7392 }
7393 SetNextWindowSize(size);
7394
7395 // Forward child flags (we allow prior settings to merge but it'll only work for adding flags)
7396 if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasChildFlags)
7397 g.NextWindowData.ChildFlags |= child_flags;
7398 else
7399 g.NextWindowData.ChildFlags = child_flags;
7400 g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasChildFlags;
7401
7402 // Build up name. If you need to append to a same child from multiple location in the ID stack, use
7403 // BeginChild(ImGuiID id) with a stable value.
7404 // FIXME: 2023/11/14: commented out shorted version. We had an issue with multiple ### in child window path names,
7405 // which the trailing hash helped workaround. e.g. "ParentName###ParentIdentifier/ChildName###ChildIdentifier" would
7406 // get hashed incorrectly by ImHashStr(), trailing _%08X somehow fixes it.
7407 const char *temp_window_name;
7408 /*if (name && parent_window->IDStack.back() == parent_window->ID)
7409 ImFormatStringToTempBuffer(&temp_window_name, NULL, "%s/%s", parent_window->Name, name); // May omit ID if in
7410 root of ID stack else*/
7411 if (name)
7412 ImFormatStringToTempBuffer(&temp_window_name, NULL, "%s/%s_%08X", parent_window->Name, name, id);
7413 else
7414 ImFormatStringToTempBuffer(&temp_window_name, NULL, "%s/%08X", parent_window->Name, id);
7415
7416 // Set style
7417 const float backup_border_size = g.Style.ChildBorderSize;
7418 if ((child_flags & ImGuiChildFlags_Borders) == 0)
7419 g.Style.ChildBorderSize = 0.0f;
7420
7421 // Begin into window
7422 const bool ret = Begin(temp_window_name, NULL, window_flags);
7423
7424 // Restore style
7425 g.Style.ChildBorderSize = backup_border_size;
7426 if (child_flags & ImGuiChildFlags_FrameStyle)
7427 {
7428 PopStyleVar(3);
7429 PopStyleColor();
7430 }
7431
7432 ImGuiWindow *child_window = g.CurrentWindow;
7433 child_window->ChildId = id;
7434
7435 // Set the cursor to handle case where the user called SetNextWindowPos()+BeginChild() manually.
7436 // While this is not really documented/defined, it seems that the expected thing to do.
7437 if (child_window->BeginCount == 1)
7438 parent_window->DC.CursorPos = child_window->Pos;
7439
7440 // Process navigation-in immediately so NavInit can run on first frame
7441 // Can enter a child if (A) it has navigable items or (B) it can be scrolled.
7442 const ImGuiID temp_id_for_activation = ImHashStr("##Child", 0, id);
7443 if (g.ActiveId == temp_id_for_activation)
7444 ClearActiveID();
7445 if (g.NavActivateId == id && !(child_flags & ImGuiChildFlags_NavFlattened) &&
7446 (child_window->DC.NavLayersActiveMask != 0 || child_window->DC.NavWindowHasScrollY))
7447 {
7448 FocusWindow(child_window);
7449 NavInitWindow(child_window, false);
7450 SetActiveID(
7451 temp_id_for_activation,
7452 child_window); // Steal ActiveId with another arbitrary id so that key-press won't activate child item
7453 g.ActiveIdSource = g.NavInputSource;
7454 }
7455 return ret;
7456}
7457
7458void ImGui::EndChild()
7459{
7460 ImGuiContext &g = *GImGui;
7461 ImGuiWindow *child_window = g.CurrentWindow;
7462
7463 const ImGuiID backup_within_end_child_id = g.WithinEndChildID;
7464 IM_ASSERT(child_window->Flags & ImGuiWindowFlags_ChildWindow); // Mismatched BeginChild()/EndChild() calls
7465
7466 g.WithinEndChildID = child_window->ID;
7467 ImVec2 child_size = child_window->Size;
7468 End();
7469 if (child_window->BeginCount == 1)
7470 {
7471 ImGuiWindow *parent_window = g.CurrentWindow;
7472 ImRect bb(parent_window->DC.CursorPos, parent_window->DC.CursorPos + child_size);
7473 ItemSize(child_size);
7474 const bool nav_flattened = (child_window->ChildFlags & ImGuiChildFlags_NavFlattened) != 0;
7475 if ((child_window->DC.NavLayersActiveMask != 0 || child_window->DC.NavWindowHasScrollY) && !nav_flattened)
7476 {
7477 ItemAdd(bb, child_window->ChildId);
7478 RenderNavCursor(bb, child_window->ChildId);
7479
7480 // When browsing a window that has no activable items (scroll only) we keep a highlight on the child (pass
7481 // g.NavId to trick into always displaying)
7482 if (child_window->DC.NavLayersActiveMask == 0 && child_window == g.NavWindow)
7483 RenderNavCursor(ImRect(bb.Min - ImVec2(2, 2), bb.Max + ImVec2(2, 2)), g.NavId,
7484 ImGuiNavRenderCursorFlags_Compact);
7485 }
7486 else
7487 {
7488 // Not navigable into
7489 // - This is a bit of a fringe use case, mostly useful for undecorated, non-scrolling contents childs, or
7490 // empty childs.
7491 // - We could later decide to not apply this path if ImGuiChildFlags_FrameStyle or ImGuiChildFlags_Borders
7492 // is set.
7493 ItemAdd(bb, child_window->ChildId, NULL, ImGuiItemFlags_NoNav);
7494
7495 // But when flattened we directly reach items, adjust active layer mask accordingly
7496 if (nav_flattened)
7497 parent_window->DC.NavLayersActiveMaskNext |= child_window->DC.NavLayersActiveMaskNext;
7498 }
7499 if (g.HoveredWindow == child_window)
7500 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow;
7501 child_window->DC.ChildItemStatusFlags = g.LastItemData.StatusFlags;
7502 // SetLastItemDataForChildWindowItem(child_window, child_window->Rect()); // Not needed, effectively done by
7503 // ItemAdd()
7504 }
7505 else
7506 {
7507 SetLastItemDataForChildWindowItem(child_window, child_window->Rect());
7508 }
7509
7510 g.WithinEndChildID = backup_within_end_child_id;
7511 g.LogLinePosY = -FLT_MAX; // To enforce a carriage return
7512}
7513
7514static void SetWindowConditionAllowFlags(ImGuiWindow *window, ImGuiCond flags, bool enabled)
7515{
7516 window->SetWindowPosAllowFlags =
7517 enabled ? (window->SetWindowPosAllowFlags | flags) : (window->SetWindowPosAllowFlags & ~flags);
7518 window->SetWindowSizeAllowFlags =
7519 enabled ? (window->SetWindowSizeAllowFlags | flags) : (window->SetWindowSizeAllowFlags & ~flags);
7520 window->SetWindowCollapsedAllowFlags =
7521 enabled ? (window->SetWindowCollapsedAllowFlags | flags) : (window->SetWindowCollapsedAllowFlags & ~flags);
7522 window->SetWindowDockAllowFlags =
7523 enabled ? (window->SetWindowDockAllowFlags | flags) : (window->SetWindowDockAllowFlags & ~flags);
7524}
7525
7526ImGuiWindow *ImGui::FindWindowByID(ImGuiID id)
7527{
7528 ImGuiContext &g = *GImGui;
7529 return (ImGuiWindow *)g.WindowsById.GetVoidPtr(id);
7530}
7531
7532ImGuiWindow *ImGui::FindWindowByName(const char *name)
7533{
7534 ImGuiID id = ImHashStr(name);
7535 return FindWindowByID(id);
7536}
7537
7538static void ApplyWindowSettings(ImGuiWindow *window, ImGuiWindowSettings *settings)
7539{
7540 const ImGuiViewport *main_viewport = ImGui::GetMainViewport();
7541 window->ViewportPos = main_viewport->Pos;
7542 if (settings->ViewportId)
7543 {
7544 window->ViewportId = settings->ViewportId;
7545 window->ViewportPos = ImVec2(settings->ViewportPos.x, settings->ViewportPos.y);
7546 }
7547 window->Pos = ImTrunc(ImVec2(settings->Pos.x + window->ViewportPos.x, settings->Pos.y + window->ViewportPos.y));
7548 if (settings->Size.x > 0 && settings->Size.y > 0)
7549 window->Size = window->SizeFull = ImTrunc(ImVec2(settings->Size.x, settings->Size.y));
7550 window->Collapsed = settings->Collapsed;
7551 window->DockId = settings->DockId;
7552 window->DockOrder = settings->DockOrder;
7553}
7554
7555static void InitOrLoadWindowSettings(ImGuiWindow *window, ImGuiWindowSettings *settings)
7556{
7557 // Initial window state with e.g. default/arbitrary window position
7558 // Use SetNextWindowPos() with the appropriate condition flag to change the initial position of a window.
7559 const ImGuiViewport *main_viewport = ImGui::GetMainViewport();
7560 window->Pos = main_viewport->Pos + ImVec2(60, 60);
7561 window->Size = window->SizeFull = ImVec2(0, 0);
7562 window->ViewportPos = main_viewport->Pos;
7563 window->SetWindowPosAllowFlags = window->SetWindowSizeAllowFlags = window->SetWindowCollapsedAllowFlags =
7564 window->SetWindowDockAllowFlags =
7565 ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing;
7566
7567 if (settings != NULL)
7568 {
7569 SetWindowConditionAllowFlags(window, ImGuiCond_FirstUseEver, false);
7570 ApplyWindowSettings(window, settings);
7571 }
7572 window->DC.CursorStartPos = window->DC.CursorMaxPos = window->DC.IdealMaxPos =
7573 window->Pos; // So first call to CalcWindowContentSizes() doesn't return crazy values
7574
7575 if ((window->Flags & ImGuiWindowFlags_AlwaysAutoResize) != 0)
7576 {
7577 window->AutoFitFramesX = window->AutoFitFramesY = 2;
7578 window->AutoFitOnlyGrows = false;
7579 }
7580 else
7581 {
7582 if (window->Size.x <= 0.0f)
7583 window->AutoFitFramesX = 2;
7584 if (window->Size.y <= 0.0f)
7585 window->AutoFitFramesY = 2;
7586 window->AutoFitOnlyGrows = (window->AutoFitFramesX > 0) || (window->AutoFitFramesY > 0);
7587 }
7588}
7589
7590static ImGuiWindow *CreateNewWindow(const char *name, ImGuiWindowFlags flags)
7591{
7592 // Create window the first time
7593 // IMGUI_DEBUG_LOG("CreateNewWindow '%s', flags = 0x%08X\n", name, flags);
7594 ImGuiContext &g = *GImGui;
7595 ImGuiWindow *window = IM_NEW(ImGuiWindow)(&g, name);
7596 window->Flags = flags;
7597 g.WindowsById.SetVoidPtr(window->ID, window);
7598
7599 ImGuiWindowSettings *settings = NULL;
7600 if (!(flags & ImGuiWindowFlags_NoSavedSettings))
7601 if ((settings = ImGui::FindWindowSettingsByWindow(window)) != 0)
7602 window->SettingsOffset = g.SettingsWindows.offset_from_ptr(settings);
7603
7604 InitOrLoadWindowSettings(window, settings);
7605
7606 if (flags & ImGuiWindowFlags_NoBringToFrontOnFocus)
7607 g.Windows.push_front(window); // Quite slow but rare and only once
7608 else
7609 g.Windows.push_back(window);
7610
7611 return window;
7612}
7613
7614static ImGuiWindow *GetWindowForTitleDisplay(ImGuiWindow *window)
7615{
7616 return window->DockNodeAsHost ? window->DockNodeAsHost->VisibleWindow : window;
7617}
7618
7619static ImGuiWindow *GetWindowForTitleAndMenuHeight(ImGuiWindow *window)
7620{
7621 return (window->DockNodeAsHost && window->DockNodeAsHost->VisibleWindow) ? window->DockNodeAsHost->VisibleWindow
7622 : window;
7623}
7624
7625static inline ImVec2 CalcWindowMinSize(ImGuiWindow *window)
7626{
7627 // We give windows non-zero minimum size to facilitate understanding problematic cases (e.g. empty popups)
7628 // FIXME: Essentially we want to restrict manual resizing to WindowMinSize+Decoration, and allow api resizing to be
7629 // smaller. Perhaps should tend further a neater test for this.
7630 ImGuiContext &g = *GImGui;
7631 ImVec2 size_min;
7632 if ((window->Flags & ImGuiWindowFlags_ChildWindow) && !(window->Flags & ImGuiWindowFlags_Popup))
7633 {
7634 size_min.x = (window->ChildFlags & ImGuiChildFlags_ResizeX) ? g.Style.WindowMinSize.x : 4.0f;
7635 size_min.y = (window->ChildFlags & ImGuiChildFlags_ResizeY) ? g.Style.WindowMinSize.y : 4.0f;
7636 }
7637 else
7638 {
7639 size_min.x = ((window->Flags & ImGuiWindowFlags_AlwaysAutoResize) == 0) ? g.Style.WindowMinSize.x : 4.0f;
7640 size_min.y = ((window->Flags & ImGuiWindowFlags_AlwaysAutoResize) == 0) ? g.Style.WindowMinSize.y : 4.0f;
7641 }
7642
7643 // Reduce artifacts with very small windows
7644 ImGuiWindow *window_for_height = GetWindowForTitleAndMenuHeight(window);
7645 size_min.y = ImMax(size_min.y, window_for_height->TitleBarHeight + window_for_height->MenuBarHeight +
7646 ImMax(0.0f, g.Style.WindowRounding - 1.0f));
7647 return size_min;
7648}
7649
7650static ImVec2 CalcWindowSizeAfterConstraint(ImGuiWindow *window, const ImVec2 &size_desired)
7651{
7652 ImGuiContext &g = *GImGui;
7653 ImVec2 new_size = size_desired;
7654 if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSizeConstraint)
7655 {
7656 // See comments in SetNextWindowSizeConstraints() for details about setting size_min an size_max.
7657 ImRect cr = g.NextWindowData.SizeConstraintRect;
7658 new_size.x = (cr.Min.x >= 0 && cr.Max.x >= 0) ? ImClamp(new_size.x, cr.Min.x, cr.Max.x) : window->SizeFull.x;
7659 new_size.y = (cr.Min.y >= 0 && cr.Max.y >= 0) ? ImClamp(new_size.y, cr.Min.y, cr.Max.y) : window->SizeFull.y;
7660 if (g.NextWindowData.SizeCallback)
7661 {
7663 data.UserData = g.NextWindowData.SizeCallbackUserData;
7664 data.Pos = window->Pos;
7665 data.CurrentSize = window->SizeFull;
7666 data.DesiredSize = new_size;
7667 g.NextWindowData.SizeCallback(&data);
7668 new_size = data.DesiredSize;
7669 }
7670 new_size.x = IM_TRUNC(new_size.x);
7671 new_size.y = IM_TRUNC(new_size.y);
7672 }
7673
7674 // Minimum size
7675 ImVec2 size_min = CalcWindowMinSize(window);
7676 return ImMax(new_size, size_min);
7677}
7678
7679static void CalcWindowContentSizes(ImGuiWindow *window, ImVec2 *content_size_current, ImVec2 *content_size_ideal)
7680{
7681 bool preserve_old_content_sizes = false;
7682 if (window->Collapsed && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0)
7683 preserve_old_content_sizes = true;
7684 else if (window->Hidden && window->HiddenFramesCannotSkipItems == 0 && window->HiddenFramesCanSkipItems > 0)
7685 preserve_old_content_sizes = true;
7686 if (preserve_old_content_sizes)
7687 {
7688 *content_size_current = window->ContentSize;
7689 *content_size_ideal = window->ContentSizeIdeal;
7690 return;
7691 }
7692
7693 content_size_current->x = (window->ContentSizeExplicit.x != 0.0f)
7694 ? window->ContentSizeExplicit.x
7695 : IM_TRUNC(window->DC.CursorMaxPos.x - window->DC.CursorStartPos.x);
7696 content_size_current->y = (window->ContentSizeExplicit.y != 0.0f)
7697 ? window->ContentSizeExplicit.y
7698 : IM_TRUNC(window->DC.CursorMaxPos.y - window->DC.CursorStartPos.y);
7699 content_size_ideal->x =
7700 (window->ContentSizeExplicit.x != 0.0f)
7701 ? window->ContentSizeExplicit.x
7702 : IM_TRUNC(ImMax(window->DC.CursorMaxPos.x, window->DC.IdealMaxPos.x) - window->DC.CursorStartPos.x);
7703 content_size_ideal->y =
7704 (window->ContentSizeExplicit.y != 0.0f)
7705 ? window->ContentSizeExplicit.y
7706 : IM_TRUNC(ImMax(window->DC.CursorMaxPos.y, window->DC.IdealMaxPos.y) - window->DC.CursorStartPos.y);
7707}
7708
7709static ImVec2 CalcWindowAutoFitSize(ImGuiWindow *window, const ImVec2 &size_contents)
7710{
7711 ImGuiContext &g = *GImGui;
7712 ImGuiStyle &style = g.Style;
7713 const float decoration_w_without_scrollbars =
7714 window->DecoOuterSizeX1 + window->DecoOuterSizeX2 - window->ScrollbarSizes.x;
7715 const float decoration_h_without_scrollbars =
7716 window->DecoOuterSizeY1 + window->DecoOuterSizeY2 - window->ScrollbarSizes.y;
7717 ImVec2 size_pad = window->WindowPadding * 2.0f;
7718 ImVec2 size_desired =
7719 size_contents + size_pad + ImVec2(decoration_w_without_scrollbars, decoration_h_without_scrollbars);
7720 if (window->Flags & ImGuiWindowFlags_Tooltip)
7721 {
7722 // Tooltip always resize
7723 return size_desired;
7724 }
7725 else
7726 {
7727 // Maximum window size is determined by the viewport size or monitor size
7728 ImVec2 size_min = CalcWindowMinSize(window);
7729 ImVec2 size_max = ImVec2(FLT_MAX, FLT_MAX);
7730
7731 // Child windows are layed within their parent (unless they are also popups/menus) and thus have no restriction
7732 if ((window->Flags & ImGuiWindowFlags_ChildWindow) == 0 || (window->Flags & ImGuiWindowFlags_Popup) != 0)
7733 {
7734 if (!window->ViewportOwned)
7735 size_max = ImGui::GetMainViewport()->WorkSize - style.DisplaySafeAreaPadding * 2.0f;
7736 const int monitor_idx = window->ViewportAllowPlatformMonitorExtend;
7737 if (monitor_idx >= 0 && monitor_idx < g.PlatformIO.Monitors.Size)
7738 size_max = g.PlatformIO.Monitors[monitor_idx].WorkSize - style.DisplaySafeAreaPadding * 2.0f;
7739 }
7740
7741 ImVec2 size_auto_fit = ImClamp(size_desired, size_min, ImMax(size_min, size_max));
7742
7743 // FIXME: CalcWindowAutoFitSize() doesn't take into account that only one axis may be auto-fit when calculating
7744 // scrollbars, we may need to compute/store three variants of size_auto_fit, for x/y/xy. Here we implement a
7745 // workaround for child windows only, but a full solution would apply to normal windows as well:
7746 if ((window->ChildFlags & ImGuiChildFlags_ResizeX) && !(window->ChildFlags & ImGuiChildFlags_ResizeY))
7747 size_auto_fit.y = window->SizeFull.y;
7748 else if (!(window->ChildFlags & ImGuiChildFlags_ResizeX) && (window->ChildFlags & ImGuiChildFlags_ResizeY))
7749 size_auto_fit.x = window->SizeFull.x;
7750
7751 // When the window cannot fit all contents (either because of constraints, either because screen is too small),
7752 // we are growing the size on the other axis to compensate for expected scrollbar. FIXME: Might turn bigger than
7753 // ViewportSize-WindowPadding.
7754 ImVec2 size_auto_fit_after_constraint = CalcWindowSizeAfterConstraint(window, size_auto_fit);
7755 bool will_have_scrollbar_x =
7756 (size_auto_fit_after_constraint.x - size_pad.x - decoration_w_without_scrollbars < size_contents.x &&
7757 !(window->Flags & ImGuiWindowFlags_NoScrollbar) &&
7758 (window->Flags & ImGuiWindowFlags_HorizontalScrollbar)) ||
7759 (window->Flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar);
7760 bool will_have_scrollbar_y =
7761 (size_auto_fit_after_constraint.y - size_pad.y - decoration_h_without_scrollbars < size_contents.y &&
7762 !(window->Flags & ImGuiWindowFlags_NoScrollbar)) ||
7763 (window->Flags & ImGuiWindowFlags_AlwaysVerticalScrollbar);
7764 if (will_have_scrollbar_x)
7765 size_auto_fit.y += style.ScrollbarSize;
7766 if (will_have_scrollbar_y)
7767 size_auto_fit.x += style.ScrollbarSize;
7768 return size_auto_fit;
7769 }
7770}
7771
7772ImVec2 ImGui::CalcWindowNextAutoFitSize(ImGuiWindow *window)
7773{
7774 ImVec2 size_contents_current;
7775 ImVec2 size_contents_ideal;
7776 CalcWindowContentSizes(window, &size_contents_current, &size_contents_ideal);
7777 ImVec2 size_auto_fit = CalcWindowAutoFitSize(window, size_contents_ideal);
7778 ImVec2 size_final = CalcWindowSizeAfterConstraint(window, size_auto_fit);
7779 return size_final;
7780}
7781
7782static ImGuiCol GetWindowBgColorIdx(ImGuiWindow *window)
7783{
7784 if (window->Flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup))
7785 return ImGuiCol_PopupBg;
7786 if ((window->Flags & ImGuiWindowFlags_ChildWindow) && !window->DockIsActive)
7787 return ImGuiCol_ChildBg;
7788 return ImGuiCol_WindowBg;
7789}
7790
7791static void CalcResizePosSizeFromAnyCorner(ImGuiWindow *window, const ImVec2 &corner_target, const ImVec2 &corner_norm,
7792 ImVec2 *out_pos, ImVec2 *out_size)
7793{
7794 ImVec2 pos_min = ImLerp(corner_target, window->Pos, corner_norm); // Expected window upper-left
7795 ImVec2 pos_max = ImLerp(window->Pos + window->Size, corner_target, corner_norm); // Expected window lower-right
7796 ImVec2 size_expected = pos_max - pos_min;
7797 ImVec2 size_constrained = CalcWindowSizeAfterConstraint(window, size_expected);
7798 *out_pos = pos_min;
7799 if (corner_norm.x == 0.0f)
7800 out_pos->x -= (size_constrained.x - size_expected.x);
7801 if (corner_norm.y == 0.0f)
7802 out_pos->y -= (size_constrained.y - size_expected.y);
7803 *out_size = size_constrained;
7804}
7805
7806// Data for resizing from resize grip / corner
7808{
7809 ImVec2 CornerPosN;
7810 ImVec2 InnerDir;
7811 int AngleMin12, AngleMax12;
7812};
7813static const ImGuiResizeGripDef resize_grip_def[4] = {
7814 {ImVec2(1, 1), ImVec2(-1, -1), 0, 3}, // Lower-right
7815 {ImVec2(0, 1), ImVec2(+1, -1), 3, 6}, // Lower-left
7816 {ImVec2(0, 0), ImVec2(+1, +1), 6, 9}, // Upper-left (Unused)
7817 {ImVec2(1, 0), ImVec2(-1, +1), 9, 12} // Upper-right (Unused)
7818};
7819
7820// Data for resizing from borders
7822{
7823 ImVec2 InnerDir; // Normal toward inside
7824 ImVec2 SegmentN1, SegmentN2; // End positions, normalized (0,0: upper left)
7825 float OuterAngle; // Angle toward outside
7826};
7827static const ImGuiResizeBorderDef resize_border_def[4] = {
7828 {ImVec2(+1, 0), ImVec2(0, 1), ImVec2(0, 0), IM_PI * 1.00f}, // Left
7829 {ImVec2(-1, 0), ImVec2(1, 0), ImVec2(1, 1), IM_PI * 0.00f}, // Right
7830 {ImVec2(0, +1), ImVec2(0, 0), ImVec2(1, 0), IM_PI * 1.50f}, // Up
7831 {ImVec2(0, -1), ImVec2(1, 1), ImVec2(0, 1), IM_PI * 0.50f} // Down
7832};
7833
7834static ImRect GetResizeBorderRect(ImGuiWindow *window, int border_n, float perp_padding, float thickness)
7835{
7836 ImRect rect = window->Rect();
7837 if (thickness == 0.0f)
7838 rect.Max -= ImVec2(1, 1);
7839 if (border_n == ImGuiDir_Left)
7840 {
7841 return ImRect(rect.Min.x - thickness, rect.Min.y + perp_padding, rect.Min.x + thickness,
7842 rect.Max.y - perp_padding);
7843 }
7844 if (border_n == ImGuiDir_Right)
7845 {
7846 return ImRect(rect.Max.x - thickness, rect.Min.y + perp_padding, rect.Max.x + thickness,
7847 rect.Max.y - perp_padding);
7848 }
7849 if (border_n == ImGuiDir_Up)
7850 {
7851 return ImRect(rect.Min.x + perp_padding, rect.Min.y - thickness, rect.Max.x - perp_padding,
7852 rect.Min.y + thickness);
7853 }
7854 if (border_n == ImGuiDir_Down)
7855 {
7856 return ImRect(rect.Min.x + perp_padding, rect.Max.y - thickness, rect.Max.x - perp_padding,
7857 rect.Max.y + thickness);
7858 }
7859 IM_ASSERT(0);
7860 return ImRect();
7861}
7862
7863// 0..3: corners (Lower-right, Lower-left, Unused, Unused)
7864ImGuiID ImGui::GetWindowResizeCornerID(ImGuiWindow *window, int n)
7865{
7866 IM_ASSERT(n >= 0 && n < 4);
7867 ImGuiID id = window->DockIsActive ? window->DockNode->HostWindow->ID : window->ID;
7868 id = ImHashStr("#RESIZE", 0, id);
7869 id = ImHashData(&n, sizeof(int), id);
7870 return id;
7871}
7872
7873// Borders (Left, Right, Up, Down)
7874ImGuiID ImGui::GetWindowResizeBorderID(ImGuiWindow *window, ImGuiDir dir)
7875{
7876 IM_ASSERT(dir >= 0 && dir < 4);
7877 int n = (int)dir + 4;
7878 ImGuiID id = window->DockIsActive ? window->DockNode->HostWindow->ID : window->ID;
7879 id = ImHashStr("#RESIZE", 0, id);
7880 id = ImHashData(&n, sizeof(int), id);
7881 return id;
7882}
7883
7884// Handle resize for: Resize Grips, Borders, Gamepad
7885// Return true when using auto-fit (double-click on resize grip)
7886static int ImGui::UpdateWindowManualResize(ImGuiWindow *window, const ImVec2 &size_auto_fit, int *border_hovered,
7887 int *border_held, int resize_grip_count, ImU32 resize_grip_col[4],
7888 const ImRect &visibility_rect)
7889{
7890 ImGuiContext &g = *GImGui;
7891 ImGuiWindowFlags flags = window->Flags;
7892
7893 if ((flags & ImGuiWindowFlags_NoResize) || (flags & ImGuiWindowFlags_AlwaysAutoResize) ||
7894 window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0)
7895 return false;
7896 if (window->WasActive ==
7897 false) // Early out to avoid running this code for e.g. a hidden implicit/fallback Debug window.
7898 return false;
7899
7900 int ret_auto_fit_mask = 0x00;
7901 const float grip_draw_size = IM_TRUNC(ImMax(g.FontSize * 1.35f, window->WindowRounding + 1.0f + g.FontSize * 0.2f));
7902 const float grip_hover_inner_size = (resize_grip_count > 0) ? IM_TRUNC(grip_draw_size * 0.75f) : 0.0f;
7903 const float grip_hover_outer_size = g.WindowsBorderHoverPadding;
7904
7905 ImRect clamp_rect = visibility_rect;
7906 const bool window_move_from_title_bar =
7907 g.IO.ConfigWindowsMoveFromTitleBarOnly && !(window->Flags & ImGuiWindowFlags_NoTitleBar);
7908 if (window_move_from_title_bar)
7909 clamp_rect.Min.y -= window->TitleBarHeight;
7910
7911 ImVec2 pos_target(FLT_MAX, FLT_MAX);
7912 ImVec2 size_target(FLT_MAX, FLT_MAX);
7913
7914 // Clip mouse interaction rectangles within the viewport rectangle (in practice the narrowing is going to happen
7915 // most of the time).
7916 // - Not narrowing would mostly benefit the situation where OS windows _without_ decoration have a threshold for
7917 // hovering when outside their limits.
7918 // This is however not the case with current backends under Win32, but a custom borderless window implementation
7919 // would benefit from it.
7920 // - When decoration are enabled we typically benefit from that distance, but then our resize elements would be
7921 // conflicting with OS resize elements, so we also narrow.
7922 // - Note that we are unable to tell if the platform setup allows hovering with a distance threshold (on Win32,
7923 // decorated window have such threshold). We only clip interaction so we overwrite window->ClipRect, cannot call
7924 // PushClipRect() yet as DrawList is not yet setup.
7925 const bool clip_with_viewport_rect = !(g.IO.BackendFlags & ImGuiBackendFlags_HasMouseHoveredViewport) ||
7926 (g.IO.MouseHoveredViewport != window->ViewportId) ||
7927 !(window->Viewport->Flags & ImGuiViewportFlags_NoDecoration);
7928 if (clip_with_viewport_rect)
7929 window->ClipRect = window->Viewport->GetMainRect();
7930
7931 // Resize grips and borders are on layer 1
7932 window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;
7933
7934 // Manual resize grips
7935 PushID("#RESIZE");
7936 for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++)
7937 {
7938 const ImGuiResizeGripDef &def = resize_grip_def[resize_grip_n];
7939 const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, def.CornerPosN);
7940
7941 // Using the FlattenChilds button flag we make the resize button accessible even if we are hovering over a child
7942 // window
7943 bool hovered, held;
7944 ImRect resize_rect(corner - def.InnerDir * grip_hover_outer_size,
7945 corner + def.InnerDir * grip_hover_inner_size);
7946 if (resize_rect.Min.x > resize_rect.Max.x)
7947 ImSwap(resize_rect.Min.x, resize_rect.Max.x);
7948 if (resize_rect.Min.y > resize_rect.Max.y)
7949 ImSwap(resize_rect.Min.y, resize_rect.Max.y);
7950 ImGuiID resize_grip_id = window->GetID(resize_grip_n); // == GetWindowResizeCornerID()
7951 ItemAdd(resize_rect, resize_grip_id, NULL, ImGuiItemFlags_NoNav);
7952 ButtonBehavior(resize_rect, resize_grip_id, &hovered, &held,
7953 ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_NoNavFocus);
7954 // GetForegroundDrawList(window)->AddRect(resize_rect.Min, resize_rect.Max, IM_COL32(255, 255, 0, 255));
7955 if (hovered || held)
7956 SetMouseCursor((resize_grip_n & 1) ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE);
7957
7958 if (held && g.IO.MouseDoubleClicked[0])
7959 {
7960 // Auto-fit when double-clicking
7961 size_target = CalcWindowSizeAfterConstraint(window, size_auto_fit);
7962 ret_auto_fit_mask = 0x03; // Both axes
7963 ClearActiveID();
7964 }
7965 else if (held)
7966 {
7967 // Resize from any of the four corners
7968 // We don't use an incremental MouseDelta but rather compute an absolute target size based on mouse position
7969 ImVec2 clamp_min =
7970 ImVec2(def.CornerPosN.x == 1.0f ? clamp_rect.Min.x : -FLT_MAX,
7971 (def.CornerPosN.y == 1.0f || (def.CornerPosN.y == 0.0f && window_move_from_title_bar))
7972 ? clamp_rect.Min.y
7973 : -FLT_MAX);
7974 ImVec2 clamp_max = ImVec2(def.CornerPosN.x == 0.0f ? clamp_rect.Max.x : +FLT_MAX,
7975 def.CornerPosN.y == 0.0f ? clamp_rect.Max.y : +FLT_MAX);
7976 ImVec2 corner_target = g.IO.MousePos - g.ActiveIdClickOffset +
7977 ImLerp(def.InnerDir * grip_hover_outer_size, def.InnerDir * -grip_hover_inner_size,
7978 def.CornerPosN); // Corner of the window corresponding to our corner grip
7979 corner_target = ImClamp(corner_target, clamp_min, clamp_max);
7980 CalcResizePosSizeFromAnyCorner(window, corner_target, def.CornerPosN, &pos_target, &size_target);
7981 }
7982
7983 // Only lower-left grip is visible before hovering/activating
7984 if (resize_grip_n == 0 || held || hovered)
7985 resize_grip_col[resize_grip_n] = GetColorU32(held ? ImGuiCol_ResizeGripActive
7986 : hovered ? ImGuiCol_ResizeGripHovered
7987 : ImGuiCol_ResizeGrip);
7988 }
7989
7990 int resize_border_mask = 0x00;
7991 if (window->Flags & ImGuiWindowFlags_ChildWindow)
7992 resize_border_mask |= ((window->ChildFlags & ImGuiChildFlags_ResizeX) ? 0x02 : 0) |
7993 ((window->ChildFlags & ImGuiChildFlags_ResizeY) ? 0x08 : 0);
7994 else
7995 resize_border_mask = g.IO.ConfigWindowsResizeFromEdges ? 0x0F : 0x00;
7996 for (int border_n = 0; border_n < 4; border_n++)
7997 {
7998 if ((resize_border_mask & (1 << border_n)) == 0)
7999 continue;
8000 const ImGuiResizeBorderDef &def = resize_border_def[border_n];
8001 const ImGuiAxis axis = (border_n == ImGuiDir_Left || border_n == ImGuiDir_Right) ? ImGuiAxis_X : ImGuiAxis_Y;
8002
8003 bool hovered, held;
8004 ImRect border_rect = GetResizeBorderRect(window, border_n, grip_hover_inner_size, g.WindowsBorderHoverPadding);
8005 ImGuiID border_id = window->GetID(border_n + 4); // == GetWindowResizeBorderID()
8006 ItemAdd(border_rect, border_id, NULL, ImGuiItemFlags_NoNav);
8007 ButtonBehavior(border_rect, border_id, &hovered, &held,
8008 ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_NoNavFocus);
8009 // GetForegroundDrawList(window)->AddRect(border_rect.Min, border_rect.Max, IM_COL32(255, 255, 0, 255));
8010 if (hovered && g.HoveredIdTimer <= WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER)
8011 hovered = false;
8012 if (hovered || held)
8013 SetMouseCursor((axis == ImGuiAxis_X) ? ImGuiMouseCursor_ResizeEW : ImGuiMouseCursor_ResizeNS);
8014 if (held && g.IO.MouseDoubleClicked[0])
8015 {
8016 // Double-clicking bottom or right border auto-fit on this axis
8017 // FIXME: CalcWindowAutoFitSize() doesn't take into account that only one side may be auto-fit when
8018 // calculating scrollbars.
8019 // FIXME: Support top and right borders: rework CalcResizePosSizeFromAnyCorner() to be reusable in both
8020 // cases.
8021 if (border_n == 1 || border_n == 3) // Right and bottom border
8022 {
8023 size_target[axis] = CalcWindowSizeAfterConstraint(window, size_auto_fit)[axis];
8024 ret_auto_fit_mask |= (1 << axis);
8025 hovered = held = false; // So border doesn't show highlighted at new position
8026 }
8027 ClearActiveID();
8028 }
8029 else if (held)
8030 {
8031 // Switch to relative resizing mode when border geometry moved (e.g. resizing a child altering parent
8032 // scroll), in order to avoid resizing feedback loop. Currently only using relative mode on resizable child
8033 // windows, as the problem to solve is more likely noticeable for them, but could apply for all windows
8034 // eventually.
8035 // FIXME: May want to generalize this idiom at lower-level, so more widgets can use it!
8036 const bool just_scrolled_manually_while_resizing =
8037 (g.WheelingWindow != NULL && g.WheelingWindowScrolledFrame == g.FrameCount &&
8038 IsWindowChildOf(window, g.WheelingWindow, false, true));
8039 if (g.ActiveIdIsJustActivated || just_scrolled_manually_while_resizing)
8040 {
8041 g.WindowResizeBorderExpectedRect = border_rect;
8042 g.WindowResizeRelativeMode = false;
8043 }
8044 if ((window->Flags & ImGuiWindowFlags_ChildWindow) &&
8045 memcmp(&g.WindowResizeBorderExpectedRect, &border_rect, sizeof(ImRect)) != 0)
8046 g.WindowResizeRelativeMode = true;
8047
8048 const ImVec2 border_curr = (window->Pos + ImMin(def.SegmentN1, def.SegmentN2) * window->Size);
8049 const float border_target_rel_mode_for_axis = border_curr[axis] + g.IO.MouseDelta[axis];
8050 const float border_target_abs_mode_for_axis =
8051 g.IO.MousePos[axis] - g.ActiveIdClickOffset[axis] +
8052 g.WindowsBorderHoverPadding; // Match ButtonBehavior() padding above.
8053
8054 // Use absolute mode position
8055 ImVec2 border_target = window->Pos;
8056 border_target[axis] = border_target_abs_mode_for_axis;
8057
8058 // Use relative mode target for child window, ignore resize when moving back toward the ideal absolute
8059 // position.
8060 bool ignore_resize = false;
8061 if (g.WindowResizeRelativeMode)
8062 {
8063 // GetForegroundDrawList()->AddText(GetMainViewport()->WorkPos, IM_COL32_WHITE, "Relative Mode");
8064 border_target[axis] = border_target_rel_mode_for_axis;
8065 if (g.IO.MouseDelta[axis] == 0.0f ||
8066 (g.IO.MouseDelta[axis] > 0.0f) ==
8067 (border_target_rel_mode_for_axis > border_target_abs_mode_for_axis))
8068 ignore_resize = true;
8069 }
8070
8071 // Clamp, apply
8072 ImVec2 clamp_min(border_n == ImGuiDir_Right ? clamp_rect.Min.x : -FLT_MAX,
8073 border_n == ImGuiDir_Down || (border_n == ImGuiDir_Up && window_move_from_title_bar)
8074 ? clamp_rect.Min.y
8075 : -FLT_MAX);
8076 ImVec2 clamp_max(border_n == ImGuiDir_Left ? clamp_rect.Max.x : +FLT_MAX,
8077 border_n == ImGuiDir_Up ? clamp_rect.Max.y : +FLT_MAX);
8078 border_target = ImClamp(border_target, clamp_min, clamp_max);
8079 if (flags & ImGuiWindowFlags_ChildWindow) // Clamp resizing of childs within parent
8080 {
8081 ImGuiWindow *parent_window = window->ParentWindow;
8082 ImGuiWindowFlags parent_flags = parent_window->Flags;
8083 ImRect border_limit_rect = parent_window->InnerRect;
8084 border_limit_rect.Expand(
8085 ImVec2(-ImMax(parent_window->WindowPadding.x, parent_window->WindowBorderSize),
8086 -ImMax(parent_window->WindowPadding.y, parent_window->WindowBorderSize)));
8087 if ((axis == ImGuiAxis_X) && ((parent_flags & (ImGuiWindowFlags_HorizontalScrollbar |
8088 ImGuiWindowFlags_AlwaysHorizontalScrollbar)) == 0 ||
8089 (parent_flags & ImGuiWindowFlags_NoScrollbar)))
8090 border_target.x = ImClamp(border_target.x, border_limit_rect.Min.x, border_limit_rect.Max.x);
8091 if ((axis == ImGuiAxis_Y) && (parent_flags & ImGuiWindowFlags_NoScrollbar))
8092 border_target.y = ImClamp(border_target.y, border_limit_rect.Min.y, border_limit_rect.Max.y);
8093 }
8094 if (!ignore_resize)
8095 CalcResizePosSizeFromAnyCorner(window, border_target, ImMin(def.SegmentN1, def.SegmentN2), &pos_target,
8096 &size_target);
8097 }
8098 if (hovered)
8099 *border_hovered = border_n;
8100 if (held)
8101 *border_held = border_n;
8102 }
8103 PopID();
8104
8105 // Restore nav layer
8106 window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
8107
8108 // Navigation resize (keyboard/gamepad)
8109 // FIXME: This cannot be moved to NavUpdateWindowing() because CalcWindowSizeAfterConstraint() need to callback into
8110 // user. Not even sure the callback works here.
8111 if (g.NavWindowingTarget && g.NavWindowingTarget->RootWindowDockTree == window)
8112 {
8113 ImVec2 nav_resize_dir;
8114 if (g.NavInputSource == ImGuiInputSource_Keyboard && g.IO.KeyShift)
8115 nav_resize_dir =
8116 GetKeyMagnitude2d(ImGuiKey_LeftArrow, ImGuiKey_RightArrow, ImGuiKey_UpArrow, ImGuiKey_DownArrow);
8117 if (g.NavInputSource == ImGuiInputSource_Gamepad)
8118 nav_resize_dir = GetKeyMagnitude2d(ImGuiKey_GamepadDpadLeft, ImGuiKey_GamepadDpadRight,
8119 ImGuiKey_GamepadDpadUp, ImGuiKey_GamepadDpadDown);
8120 if (nav_resize_dir.x != 0.0f || nav_resize_dir.y != 0.0f)
8121 {
8122 const float NAV_RESIZE_SPEED = 600.0f;
8123 const float resize_step = NAV_RESIZE_SPEED * g.IO.DeltaTime *
8124 ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y);
8125 g.NavWindowingAccumDeltaSize += nav_resize_dir * resize_step;
8126 g.NavWindowingAccumDeltaSize =
8127 ImMax(g.NavWindowingAccumDeltaSize,
8128 clamp_rect.Min - window->Pos -
8129 window->Size); // We need Pos+Size >= clmap_rect.Min, so Size >= clmap_rect.Min - Pos, so
8130 // size_delta >= clmap_rect.Min - window->Pos - window->Size
8131 g.NavWindowingToggleLayer = false;
8132 g.NavHighlightItemUnderNav = true;
8133 resize_grip_col[0] = GetColorU32(ImGuiCol_ResizeGripActive);
8134 ImVec2 accum_floored = ImTrunc(g.NavWindowingAccumDeltaSize);
8135 if (accum_floored.x != 0.0f || accum_floored.y != 0.0f)
8136 {
8137 // FIXME-NAV: Should store and accumulate into a separate size buffer to handle sizing constraints
8138 // properly, right now a constraint will make us stuck.
8139 size_target = CalcWindowSizeAfterConstraint(window, window->SizeFull + accum_floored);
8140 g.NavWindowingAccumDeltaSize -= accum_floored;
8141 }
8142 }
8143 }
8144
8145 // Apply back modified position/size to window
8146 const ImVec2 curr_pos = window->Pos;
8147 const ImVec2 curr_size = window->SizeFull;
8148 if (size_target.x != FLT_MAX && (window->Size.x != size_target.x || window->SizeFull.x != size_target.x))
8149 window->Size.x = window->SizeFull.x = size_target.x;
8150 if (size_target.y != FLT_MAX && (window->Size.y != size_target.y || window->SizeFull.y != size_target.y))
8151 window->Size.y = window->SizeFull.y = size_target.y;
8152 if (pos_target.x != FLT_MAX && window->Pos.x != ImTrunc(pos_target.x))
8153 window->Pos.x = ImTrunc(pos_target.x);
8154 if (pos_target.y != FLT_MAX && window->Pos.y != ImTrunc(pos_target.y))
8155 window->Pos.y = ImTrunc(pos_target.y);
8156 if (curr_pos.x != window->Pos.x || curr_pos.y != window->Pos.y || curr_size.x != window->SizeFull.x ||
8157 curr_size.y != window->SizeFull.y)
8158 MarkIniSettingsDirty(window);
8159
8160 // Recalculate next expected border expected coordinates
8161 if (*border_held != -1)
8162 g.WindowResizeBorderExpectedRect =
8163 GetResizeBorderRect(window, *border_held, grip_hover_inner_size, g.WindowsBorderHoverPadding);
8164
8165 return ret_auto_fit_mask;
8166}
8167
8168static inline void ClampWindowPos(ImGuiWindow *window, const ImRect &visibility_rect)
8169{
8170 ImGuiContext &g = *GImGui;
8171 ImVec2 size_for_clamping = window->Size;
8172 if (g.IO.ConfigWindowsMoveFromTitleBarOnly && window->DockNodeAsHost)
8173 size_for_clamping.y =
8174 ImGui::GetFrameHeight(); // Not using window->TitleBarHeight() as DockNodeAsHost will report 0.0f here.
8175 else if (g.IO.ConfigWindowsMoveFromTitleBarOnly && !(window->Flags & ImGuiWindowFlags_NoTitleBar))
8176 size_for_clamping.y = window->TitleBarHeight;
8177 window->Pos = ImClamp(window->Pos, visibility_rect.Min - size_for_clamping, visibility_rect.Max);
8178}
8179
8180static void RenderWindowOuterSingleBorder(ImGuiWindow *window, int border_n, ImU32 border_col, float border_size)
8181{
8182 const ImGuiResizeBorderDef &def = resize_border_def[border_n];
8183 const float rounding = window->WindowRounding;
8184 const ImRect border_r = GetResizeBorderRect(window, border_n, rounding, 0.0f);
8185 window->DrawList->PathArcTo(ImLerp(border_r.Min, border_r.Max, def.SegmentN1) + ImVec2(0.5f, 0.5f) +
8186 def.InnerDir * rounding,
8187 rounding, def.OuterAngle - IM_PI * 0.25f, def.OuterAngle);
8188 window->DrawList->PathArcTo(ImLerp(border_r.Min, border_r.Max, def.SegmentN2) + ImVec2(0.5f, 0.5f) +
8189 def.InnerDir * rounding,
8190 rounding, def.OuterAngle, def.OuterAngle + IM_PI * 0.25f);
8191 window->DrawList->PathStroke(border_col, ImDrawFlags_None, border_size);
8192}
8193
8194static void ImGui::RenderWindowOuterBorders(ImGuiWindow *window)
8195{
8196 ImGuiContext &g = *GImGui;
8197 const float border_size = window->WindowBorderSize;
8198 const ImU32 border_col = GetColorU32(ImGuiCol_Border);
8199 if (border_size > 0.0f && (window->Flags & ImGuiWindowFlags_NoBackground) == 0)
8200 window->DrawList->AddRect(window->Pos, window->Pos + window->Size, border_col, window->WindowRounding, 0,
8201 window->WindowBorderSize);
8202 else if (border_size > 0.0f)
8203 {
8204 if (window->ChildFlags &
8205 ImGuiChildFlags_ResizeX) // Similar code as 'resize_border_mask' computation in UpdateWindowManualResize()
8206 // but we specifically only always draw explicit child resize border.
8207 RenderWindowOuterSingleBorder(window, 1, border_col, border_size);
8208 if (window->ChildFlags & ImGuiChildFlags_ResizeY)
8209 RenderWindowOuterSingleBorder(window, 3, border_col, border_size);
8210 }
8211 if (window->ResizeBorderHovered != -1 || window->ResizeBorderHeld != -1)
8212 {
8213 const int border_n = (window->ResizeBorderHeld != -1) ? window->ResizeBorderHeld : window->ResizeBorderHovered;
8214 const ImU32 border_col_resizing =
8215 GetColorU32((window->ResizeBorderHeld != -1) ? ImGuiCol_SeparatorActive : ImGuiCol_SeparatorHovered);
8216 RenderWindowOuterSingleBorder(window, border_n, border_col_resizing,
8217 ImMax(2.0f, window->WindowBorderSize)); // Thicker than usual
8218 }
8219 if (g.Style.FrameBorderSize > 0 && !(window->Flags & ImGuiWindowFlags_NoTitleBar) && !window->DockIsActive)
8220 {
8221 float y = window->Pos.y + window->TitleBarHeight - 1;
8222 window->DrawList->AddLine(ImVec2(window->Pos.x + border_size * 0.5f, y),
8223 ImVec2(window->Pos.x + window->Size.x - border_size * 0.5f, y), border_col,
8224 g.Style.FrameBorderSize);
8225 }
8226}
8227
8228// Draw background and borders
8229// Draw and handle scrollbars
8230void ImGui::RenderWindowDecorations(ImGuiWindow *window, const ImRect &title_bar_rect, bool title_bar_is_highlight,
8231 bool handle_borders_and_resize_grips, int resize_grip_count,
8232 const ImU32 resize_grip_col[4], float resize_grip_draw_size)
8233{
8234 ImGuiContext &g = *GImGui;
8235 ImGuiStyle &style = g.Style;
8236 ImGuiWindowFlags flags = window->Flags;
8237
8238 // Ensure that Scrollbar() doesn't read last frame's SkipItems
8239 IM_ASSERT(window->BeginCount == 0);
8240 window->SkipItems = false;
8241 window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;
8242
8243 // Draw window + handle manual resize
8244 // As we highlight the title bar when want_focus is set, multiple reappearing windows will have their title bar
8245 // highlighted on their reappearing frame.
8246 const float window_rounding = window->WindowRounding;
8247 const float window_border_size = window->WindowBorderSize;
8248 if (window->Collapsed)
8249 {
8250 // Title bar only
8251 const float backup_border_size = style.FrameBorderSize;
8252 g.Style.FrameBorderSize = window->WindowBorderSize;
8253 ImU32 title_bar_col = GetColorU32((title_bar_is_highlight && g.NavCursorVisible) ? ImGuiCol_TitleBgActive
8254 : ImGuiCol_TitleBgCollapsed);
8255 if (window->ViewportOwned)
8256 title_bar_col |=
8257 IM_COL32_A_MASK; // No alpha (we don't support is_docking_transparent_payload here because simpler and
8258 // less meaningful, but could with a bit of code shuffle/reuse)
8259 RenderFrame(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, true, window_rounding);
8260 g.Style.FrameBorderSize = backup_border_size;
8261 }
8262 else
8263 {
8264 // Window background
8265 if (!(flags & ImGuiWindowFlags_NoBackground))
8266 {
8267 bool is_docking_transparent_payload = false;
8268 if (g.DragDropActive && (g.FrameCount - g.DragDropAcceptFrameCount) <= 1 &&
8269 g.IO.ConfigDockingTransparentPayload)
8270 if (g.DragDropPayload.IsDataType(IMGUI_PAYLOAD_TYPE_WINDOW) &&
8271 *(ImGuiWindow **)g.DragDropPayload.Data == window)
8272 is_docking_transparent_payload = true;
8273
8274 ImU32 bg_col = GetColorU32(GetWindowBgColorIdx(window));
8275 if (window->ViewportOwned)
8276 {
8277 bg_col |= IM_COL32_A_MASK; // No alpha
8278 if (is_docking_transparent_payload)
8279 window->Viewport->Alpha *= DOCKING_TRANSPARENT_PAYLOAD_ALPHA;
8280 }
8281 else
8282 {
8283 // Adjust alpha. For docking
8284 bool override_alpha = false;
8285 float alpha = 1.0f;
8286 if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasBgAlpha)
8287 {
8288 alpha = g.NextWindowData.BgAlphaVal;
8289 override_alpha = true;
8290 }
8291 if (is_docking_transparent_payload)
8292 {
8293 alpha *= DOCKING_TRANSPARENT_PAYLOAD_ALPHA; // FIXME-DOCK: Should that be an override?
8294 override_alpha = true;
8295 }
8296 if (override_alpha)
8297 bg_col = (bg_col & ~IM_COL32_A_MASK) | (IM_F32_TO_INT8_SAT(alpha) << IM_COL32_A_SHIFT);
8298 }
8299
8300 // Render, for docked windows and host windows we ensure bg goes before decorations
8301 if (window->DockIsActive)
8302 window->DockNode->LastBgColor = bg_col;
8303 ImDrawList *bg_draw_list = window->DockIsActive ? window->DockNode->HostWindow->DrawList : window->DrawList;
8304 if (window->DockIsActive || (flags & ImGuiWindowFlags_DockNodeHost))
8305 bg_draw_list->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_BG);
8306 bg_draw_list->AddRectFilled(window->Pos + ImVec2(0, window->TitleBarHeight), window->Pos + window->Size,
8307 bg_col, window_rounding,
8308 (flags & ImGuiWindowFlags_NoTitleBar) ? 0 : ImDrawFlags_RoundCornersBottom);
8309 if (window->DockIsActive || (flags & ImGuiWindowFlags_DockNodeHost))
8310 bg_draw_list->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_FG);
8311 }
8312 if (window->DockIsActive)
8313 window->DockNode->IsBgDrawnThisFrame = true;
8314
8315 // Title bar
8316 // (when docked, DockNode are drawing their own title bar. Individual windows however do NOT set the _NoTitleBar
8317 // flag, in order for their pos/size to be matching their undocking state.)
8318 if (!(flags & ImGuiWindowFlags_NoTitleBar) && !window->DockIsActive)
8319 {
8320 ImU32 title_bar_col = GetColorU32(title_bar_is_highlight ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg);
8321 if (window->ViewportOwned)
8322 title_bar_col |= IM_COL32_A_MASK; // No alpha
8323 window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, window_rounding,
8324 ImDrawFlags_RoundCornersTop);
8325 }
8326
8327 // Menu bar
8328 if (flags & ImGuiWindowFlags_MenuBar)
8329 {
8330 ImRect menu_bar_rect = window->MenuBarRect();
8331 menu_bar_rect.ClipWith(window->Rect()); // Soft clipping, in particular child window don't have minimum size
8332 // covering the menu bar so this is useful for them.
8333 window->DrawList->AddRectFilled(menu_bar_rect.Min, menu_bar_rect.Max, GetColorU32(ImGuiCol_MenuBarBg),
8334 (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f,
8335 ImDrawFlags_RoundCornersTop);
8336 if (style.FrameBorderSize > 0.0f && menu_bar_rect.Max.y < window->Pos.y + window->Size.y)
8337 window->DrawList->AddLine(menu_bar_rect.GetBL() + ImVec2(window_border_size * 0.5f, 0.0f),
8338 menu_bar_rect.GetBR() - ImVec2(window_border_size * 0.5f, 0.0f),
8339 GetColorU32(ImGuiCol_Border), style.FrameBorderSize);
8340 }
8341
8342 // Docking: Unhide tab bar (small triangle in the corner), drag from small triangle to quickly undock
8343 ImGuiDockNode *node = window->DockNode;
8344 if (window->DockIsActive && node->IsHiddenTabBar() && !node->IsNoTabBar())
8345 {
8346 float unhide_sz_draw = ImTrunc(g.FontSize * 0.70f);
8347 float unhide_sz_hit = ImTrunc(g.FontSize * 0.55f);
8348 ImVec2 p = node->Pos;
8349 ImRect r(p, p + ImVec2(unhide_sz_hit, unhide_sz_hit));
8350 ImGuiID unhide_id = window->GetID("#UNHIDE");
8351 KeepAliveID(unhide_id);
8352 bool hovered, held;
8353 if (ButtonBehavior(r, unhide_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren))
8354 node->WantHiddenTabBarToggle = true;
8355 else if (held && IsMouseDragging(0))
8356 StartMouseMovingWindowOrNode(
8357 window, node, true); // Undock from tab-bar triangle = same as window/collapse menu button
8358
8359 // FIXME-DOCK: Ideally we'd use ImGuiCol_TitleBgActive/ImGuiCol_TitleBg here, but neither is guaranteed to
8360 // be visible enough at this sort of size..
8361 ImU32 col = GetColorU32(((held && hovered) || (node->IsFocused && !hovered)) ? ImGuiCol_ButtonActive
8362 : hovered ? ImGuiCol_ButtonHovered
8363 : ImGuiCol_Button);
8364 window->DrawList->AddTriangleFilled(p, p + ImVec2(unhide_sz_draw, 0.0f), p + ImVec2(0.0f, unhide_sz_draw),
8365 col);
8366 }
8367
8368 // Scrollbars
8369 if (window->ScrollbarX)
8370 Scrollbar(ImGuiAxis_X);
8371 if (window->ScrollbarY)
8372 Scrollbar(ImGuiAxis_Y);
8373
8374 // Render resize grips (after their input handling so we don't have a frame of latency)
8375 if (handle_borders_and_resize_grips && !(flags & ImGuiWindowFlags_NoResize))
8376 {
8377 for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++)
8378 {
8379 const ImU32 col = resize_grip_col[resize_grip_n];
8380 if ((col & IM_COL32_A_MASK) == 0)
8381 continue;
8382 const ImGuiResizeGripDef &grip = resize_grip_def[resize_grip_n];
8383 const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, grip.CornerPosN);
8384 const float border_inner = IM_ROUND(window_border_size * 0.5f);
8385 window->DrawList->PathLineTo(
8386 corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(border_inner, resize_grip_draw_size)
8387 : ImVec2(resize_grip_draw_size, border_inner)));
8388 window->DrawList->PathLineTo(
8389 corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(resize_grip_draw_size, border_inner)
8390 : ImVec2(border_inner, resize_grip_draw_size)));
8391 window->DrawList->PathArcToFast(ImVec2(corner.x + grip.InnerDir.x * (window_rounding + border_inner),
8392 corner.y + grip.InnerDir.y * (window_rounding + border_inner)),
8393 window_rounding, grip.AngleMin12, grip.AngleMax12);
8394 window->DrawList->PathFillConvex(col);
8395 }
8396 }
8397
8398 // Borders (for dock node host they will be rendered over after the tab bar)
8399 if (handle_borders_and_resize_grips && !window->DockNodeAsHost)
8400 RenderWindowOuterBorders(window);
8401 }
8402 window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
8403}
8404
8405// When inside a dock node, this is handled in DockNodeCalcTabBarLayout() instead.
8406// Render title text, collapse button, close button
8407void ImGui::RenderWindowTitleBarContents(ImGuiWindow *window, const ImRect &title_bar_rect, const char *name,
8408 bool *p_open)
8409{
8410 ImGuiContext &g = *GImGui;
8411 ImGuiStyle &style = g.Style;
8412 ImGuiWindowFlags flags = window->Flags;
8413
8414 const bool has_close_button = (p_open != NULL);
8415 const bool has_collapse_button =
8416 !(flags & ImGuiWindowFlags_NoCollapse) && (style.WindowMenuButtonPosition != ImGuiDir_None);
8417
8418 // Close & Collapse button are on the Menu NavLayer and don't default focus (unless there's nothing else on that
8419 // layer)
8420 // FIXME-NAV: Might want (or not?) to set the equivalent of ImGuiButtonFlags_NoNavFocus so that mouse clicks on
8421 // standard title bar items don't necessarily set nav/keyboard ref?
8422 const ImGuiItemFlags item_flags_backup = g.CurrentItemFlags;
8423 g.CurrentItemFlags |= ImGuiItemFlags_NoNavDefaultFocus;
8424 window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;
8425
8426 // Layout buttons
8427 // FIXME: Would be nice to generalize the subtleties expressed here into reusable code.
8428 float pad_l = style.FramePadding.x;
8429 float pad_r = style.FramePadding.x;
8430 float button_sz = g.FontSize;
8431 ImVec2 close_button_pos;
8432 ImVec2 collapse_button_pos;
8433 if (has_close_button)
8434 {
8435 close_button_pos =
8436 ImVec2(title_bar_rect.Max.x - pad_r - button_sz, title_bar_rect.Min.y + style.FramePadding.y);
8437 pad_r += button_sz + style.ItemInnerSpacing.x;
8438 }
8439 if (has_collapse_button && style.WindowMenuButtonPosition == ImGuiDir_Right)
8440 {
8441 collapse_button_pos =
8442 ImVec2(title_bar_rect.Max.x - pad_r - button_sz, title_bar_rect.Min.y + style.FramePadding.y);
8443 pad_r += button_sz + style.ItemInnerSpacing.x;
8444 }
8445 if (has_collapse_button && style.WindowMenuButtonPosition == ImGuiDir_Left)
8446 {
8447 collapse_button_pos = ImVec2(title_bar_rect.Min.x + pad_l, title_bar_rect.Min.y + style.FramePadding.y);
8448 pad_l += button_sz + style.ItemInnerSpacing.x;
8449 }
8450
8451 // Collapse button (submitting first so it gets priority when choosing a navigation init fallback)
8452 if (has_collapse_button)
8453 if (CollapseButton(window->GetID("#COLLAPSE"), collapse_button_pos, NULL))
8454 window->WantCollapseToggle =
8455 true; // Defer actual collapsing to next frame as we are too far in the Begin() function
8456
8457 // Close button
8458 if (has_close_button)
8459 if (CloseButton(window->GetID("#CLOSE"), close_button_pos))
8460 *p_open = false;
8461
8462 window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
8463 g.CurrentItemFlags = item_flags_backup;
8464
8465 // Title bar text (with: horizontal alignment, avoiding collapse/close button, optional "unsaved document" marker)
8466 // FIXME: Refactor text alignment facilities along with RenderText helpers, this is WAY too much messy code..
8467 const float marker_size_x = (flags & ImGuiWindowFlags_UnsavedDocument) ? button_sz * 0.80f : 0.0f;
8468 const ImVec2 text_size = CalcTextSize(name, NULL, true) + ImVec2(marker_size_x, 0.0f);
8469
8470 // As a nice touch we try to ensure that centered title text doesn't get affected by visibility of Close/Collapse
8471 // button, while uncentered title text will still reach edges correctly.
8472 if (pad_l > style.FramePadding.x)
8473 pad_l += g.Style.ItemInnerSpacing.x;
8474 if (pad_r > style.FramePadding.x)
8475 pad_r += g.Style.ItemInnerSpacing.x;
8476 if (style.WindowTitleAlign.x > 0.0f && style.WindowTitleAlign.x < 1.0f)
8477 {
8478 float centerness =
8479 ImSaturate(1.0f - ImFabs(style.WindowTitleAlign.x - 0.5f) * 2.0f); // 0.0f on either edges, 1.0f on center
8480 float pad_extend = ImMin(ImMax(pad_l, pad_r), title_bar_rect.GetWidth() - pad_l - pad_r - text_size.x);
8481 pad_l = ImMax(pad_l, pad_extend * centerness);
8482 pad_r = ImMax(pad_r, pad_extend * centerness);
8483 }
8484
8485 ImRect layout_r(title_bar_rect.Min.x + pad_l, title_bar_rect.Min.y, title_bar_rect.Max.x - pad_r,
8486 title_bar_rect.Max.y);
8487 ImRect clip_r(layout_r.Min.x, layout_r.Min.y,
8488 ImMin(layout_r.Max.x + g.Style.ItemInnerSpacing.x, title_bar_rect.Max.x), layout_r.Max.y);
8489 if (flags & ImGuiWindowFlags_UnsavedDocument)
8490 {
8491 ImVec2 marker_pos;
8492 marker_pos.x =
8493 ImClamp(layout_r.Min.x + (layout_r.GetWidth() - text_size.x) * style.WindowTitleAlign.x + text_size.x,
8494 layout_r.Min.x, layout_r.Max.x);
8495 marker_pos.y = (layout_r.Min.y + layout_r.Max.y) * 0.5f;
8496 if (marker_pos.x > layout_r.Min.x)
8497 {
8498 RenderBullet(window->DrawList, marker_pos, GetColorU32(ImGuiCol_Text));
8499 clip_r.Max.x = ImMin(clip_r.Max.x, marker_pos.x - (int)(marker_size_x * 0.5f));
8500 }
8501 }
8502 // if (g.IO.KeyShift) window->DrawList->AddRect(layout_r.Min, layout_r.Max, IM_COL32(255, 128, 0, 255)); // [DEBUG]
8503 // if (g.IO.KeyCtrl) window->DrawList->AddRect(clip_r.Min, clip_r.Max, IM_COL32(255, 128, 0, 255)); // [DEBUG]
8504 RenderTextClipped(layout_r.Min, layout_r.Max, name, NULL, &text_size, style.WindowTitleAlign, &clip_r);
8505}
8506
8507void ImGui::UpdateWindowParentAndRootLinks(ImGuiWindow *window, ImGuiWindowFlags flags, ImGuiWindow *parent_window)
8508{
8509 window->ParentWindow = parent_window;
8510 window->RootWindow = window->RootWindowPopupTree = window->RootWindowDockTree =
8511 window->RootWindowForTitleBarHighlight = window->RootWindowForNav = window;
8512 if (parent_window && (flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Tooltip))
8513 {
8514 window->RootWindowDockTree = parent_window->RootWindowDockTree;
8515 if (!window->DockIsActive && !(parent_window->Flags & ImGuiWindowFlags_DockNodeHost))
8516 window->RootWindow = parent_window->RootWindow;
8517 }
8518 if (parent_window && (flags & ImGuiWindowFlags_Popup))
8519 window->RootWindowPopupTree = parent_window->RootWindowPopupTree;
8520 if (parent_window && !(flags & ImGuiWindowFlags_Modal) &&
8521 (flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup))) // FIXME: simply use _NoTitleBar ?
8522 window->RootWindowForTitleBarHighlight = parent_window->RootWindowForTitleBarHighlight;
8523 while (window->RootWindowForNav->ChildFlags & ImGuiChildFlags_NavFlattened)
8524 {
8525 IM_ASSERT(window->RootWindowForNav->ParentWindow != NULL);
8526 window->RootWindowForNav = window->RootWindowForNav->ParentWindow;
8527 }
8528}
8529
8530// [EXPERIMENTAL] Called by Begin(). NextWindowData is valid at this point.
8531// This is designed as a toy/test-bed for
8532void ImGui::UpdateWindowSkipRefresh(ImGuiWindow *window)
8533{
8534 ImGuiContext &g = *GImGui;
8535 window->SkipRefresh = false;
8536 if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasRefreshPolicy) == 0)
8537 return;
8538 if (g.NextWindowData.RefreshFlagsVal & ImGuiWindowRefreshFlags_TryToAvoidRefresh)
8539 {
8540 // FIXME-IDLE: Tests for e.g. mouse clicks or keyboard while focused.
8541 if (window->Appearing) // If currently appearing
8542 return;
8543 if (window->Hidden) // If was hidden (previous frame)
8544 return;
8545 if ((g.NextWindowData.RefreshFlagsVal & ImGuiWindowRefreshFlags_RefreshOnHover) && g.HoveredWindow)
8546 if (window->RootWindow == g.HoveredWindow->RootWindow ||
8547 IsWindowWithinBeginStackOf(g.HoveredWindow->RootWindow, window))
8548 return;
8549 if ((g.NextWindowData.RefreshFlagsVal & ImGuiWindowRefreshFlags_RefreshOnFocus) && g.NavWindow)
8550 if (window->RootWindow == g.NavWindow->RootWindow ||
8551 IsWindowWithinBeginStackOf(g.NavWindow->RootWindow, window))
8552 return;
8553 window->DrawList = NULL;
8554 window->SkipRefresh = true;
8555 }
8556}
8557
8558static void SetWindowActiveForSkipRefresh(ImGuiWindow *window)
8559{
8560 window->Active = true;
8561 for (ImGuiWindow *child : window->DC.ChildWindows)
8562 if (!child->Hidden)
8563 {
8564 child->Active = child->SkipRefresh = true;
8565 SetWindowActiveForSkipRefresh(child);
8566 }
8567}
8568
8569// Push a new Dear ImGui window to add widgets to.
8570// - A default window called "Debug" is automatically stacked at the beginning of every frame so you can use widgets
8571// without explicitly calling a Begin/End pair.
8572// - Begin/End can be called multiple times during the frame with the same window name to append content.
8573// - The window name is used as a unique identifier to preserve window information across frames (and save rudimentary
8574// information to the .ini file).
8575// You can use the "##" or "###" markers to use the same label with different id, or same id with different label. See
8576// documentation at the top of this file.
8577// - Return false when window is collapsed, so you can early out in your code. You always need to call ImGui::End() even
8578// if false is returned.
8579// - Passing 'bool* p_open' displays a Close button on the upper-right corner of the window, the pointed value will be
8580// set to false when the button is pressed.
8581bool ImGui::Begin(const char *name, bool *p_open, ImGuiWindowFlags flags)
8582{
8583 ImGuiContext &g = *GImGui;
8584 const ImGuiStyle &style = g.Style;
8585 IM_ASSERT(name != NULL && name[0] != '\0'); // Window name required
8586 IM_ASSERT(g.WithinFrameScope); // Forgot to call ImGui::NewFrame()
8587 IM_ASSERT(
8588 g.FrameCountEnded !=
8589 g.FrameCount); // Called ImGui::Render() or ImGui::EndFrame() and haven't called ImGui::NewFrame() again yet
8590
8591 // Find or create
8592 ImGuiWindow *window = FindWindowByName(name);
8593 const bool window_just_created = (window == NULL);
8594 if (window_just_created)
8595 window = CreateNewWindow(name, flags);
8596
8597 // [DEBUG] Debug break requested by user
8598 if (g.DebugBreakInWindow == window->ID)
8599 IM_DEBUG_BREAK();
8600
8601 // Automatically disable manual moving/resizing when NoInputs is set
8602 if ((flags & ImGuiWindowFlags_NoInputs) == ImGuiWindowFlags_NoInputs)
8603 flags |= ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
8604
8605 const int current_frame = g.FrameCount;
8606 const bool first_begin_of_the_frame = (window->LastFrameActive != current_frame);
8607 window->IsFallbackWindow = (g.CurrentWindowStack.Size == 0 && g.WithinFrameScopeWithImplicitWindow);
8608
8609 // Update the Appearing flag (note: the BeginDocked() path may also set this to true later)
8610 bool window_just_activated_by_user =
8611 (window->LastFrameActive <
8612 current_frame - 1); // Not using !WasActive because the implicit "Debug" window would always toggle off->on
8613 if (flags & ImGuiWindowFlags_Popup)
8614 {
8615 ImGuiPopupData &popup_ref = g.OpenPopupStack[g.BeginPopupStack.Size];
8616 window_just_activated_by_user |=
8617 (window->PopupId !=
8618 popup_ref.PopupId); // We recycle popups so treat window as activated if popup id changed
8619 window_just_activated_by_user |= (window != popup_ref.Window);
8620 }
8621
8622 // Update Flags, LastFrameActive, BeginOrderXXX fields
8623 const bool window_was_appearing = window->Appearing;
8624 if (first_begin_of_the_frame)
8625 {
8626 UpdateWindowInFocusOrderList(window, window_just_created, flags);
8627 window->Appearing = window_just_activated_by_user;
8628 if (window->Appearing)
8629 SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true);
8630 window->FlagsPreviousFrame = window->Flags;
8631 window->Flags = (ImGuiWindowFlags)flags;
8632 window->ChildFlags =
8633 (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasChildFlags) ? g.NextWindowData.ChildFlags : 0;
8634 window->LastFrameActive = current_frame;
8635 window->LastTimeActive = (float)g.Time;
8636 window->BeginOrderWithinParent = 0;
8637 window->BeginOrderWithinContext = (short)(g.WindowsActiveCount++);
8638 }
8639 else
8640 {
8641 flags = window->Flags;
8642 }
8643
8644 // Docking
8645 // (NB: during the frame dock nodes are created, it is possible that (window->DockIsActive == false) even though
8646 // (window->DockNode->Windows.Size > 1)
8647 IM_ASSERT(window->DockNode == NULL || window->DockNodeAsHost == NULL); // Cannot be both
8648 if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasDock)
8649 SetWindowDock(window, g.NextWindowData.DockId, g.NextWindowData.DockCond);
8650 if (first_begin_of_the_frame)
8651 {
8652 bool has_dock_node = (window->DockId != 0 || window->DockNode != NULL);
8653 bool new_auto_dock_node = !has_dock_node && GetWindowAlwaysWantOwnTabBar(window);
8654 bool dock_node_was_visible = window->DockNodeIsVisible;
8655 bool dock_tab_was_visible = window->DockTabIsVisible;
8656 if (has_dock_node || new_auto_dock_node)
8657 {
8658 BeginDocked(window, p_open);
8659 flags = window->Flags;
8660 if (window->DockIsActive)
8661 {
8662 IM_ASSERT(window->DockNode != NULL);
8663 g.NextWindowData.HasFlags &=
8664 ~ImGuiNextWindowDataFlags_HasSizeConstraint; // Docking currently override constraints
8665 }
8666
8667 // Amend the Appearing flag
8668 if (window->DockTabIsVisible && !dock_tab_was_visible && dock_node_was_visible && !window->Appearing &&
8669 !window_was_appearing)
8670 {
8671 window->Appearing = true;
8672 SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true);
8673 }
8674 }
8675 else
8676 {
8677 window->DockIsActive = window->DockNodeIsVisible = window->DockTabIsVisible = false;
8678 }
8679 }
8680
8681 // Parent window is latched only on the first call to Begin() of the frame, so further append-calls can be done from
8682 // a different window stack
8683 ImGuiWindow *parent_window_in_stack = (window->DockIsActive && window->DockNode->HostWindow)
8684 ? window->DockNode->HostWindow
8685 : g.CurrentWindowStack.empty() ? NULL
8686 : g.CurrentWindowStack.back().Window;
8687 ImGuiWindow *parent_window =
8688 first_begin_of_the_frame
8689 ? ((flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup)) ? parent_window_in_stack : NULL)
8690 : window->ParentWindow;
8691 IM_ASSERT(parent_window != NULL || !(flags & ImGuiWindowFlags_ChildWindow));
8692
8693 // We allow window memory to be compacted so recreate the base stack when needed.
8694 if (window->IDStack.Size == 0)
8695 window->IDStack.push_back(window->ID);
8696
8697 // Add to stack
8698 g.CurrentWindow = window;
8699 g.CurrentWindowStack.resize(g.CurrentWindowStack.Size + 1);
8700 ImGuiWindowStackData &window_stack_data = g.CurrentWindowStack.back();
8701 window_stack_data.Window = window;
8702 window_stack_data.ParentLastItemDataBackup = g.LastItemData;
8703 window_stack_data.DisabledOverrideReenable =
8704 (flags & ImGuiWindowFlags_Tooltip) && (g.CurrentItemFlags & ImGuiItemFlags_Disabled);
8705 window_stack_data.DisabledOverrideReenableAlphaBackup = 0.0f;
8706 ErrorRecoveryStoreState(&window_stack_data.StackSizesInBegin);
8707 g.StackSizesInBeginForCurrentWindow = &window_stack_data.StackSizesInBegin;
8708 if (flags & ImGuiWindowFlags_ChildMenu)
8709 g.BeginMenuDepth++;
8710
8711 // Update ->RootWindow and others pointers (before any possible call to FocusWindow)
8712 if (first_begin_of_the_frame)
8713 {
8714 UpdateWindowParentAndRootLinks(window, flags, parent_window);
8715 window->ParentWindowInBeginStack = parent_window_in_stack;
8716
8717 // Focus route
8718 // There's little point to expose a flag to set this: because the interesting cases won't be using
8719 // parent_window_in_stack, Use for e.g. linking a tool window in a standalone viewport to a document window,
8720 // regardless of their Begin() stack parenting. (#6798)
8721 window->ParentWindowForFocusRoute = (window->RootWindow != window) ? parent_window_in_stack : NULL;
8722 if (window->ParentWindowForFocusRoute == NULL && window->DockNode != NULL)
8723 if (window->DockNode->MergedFlags & ImGuiDockNodeFlags_DockedWindowsInFocusRoute)
8724 window->ParentWindowForFocusRoute = window->DockNode->HostWindow;
8725
8726 // Override with SetNextWindowClass() field or direct call to SetWindowParentWindowForFocusRoute()
8727 if (window->WindowClass.FocusRouteParentWindowId != 0)
8728 {
8729 window->ParentWindowForFocusRoute = FindWindowByID(window->WindowClass.FocusRouteParentWindowId);
8730 IM_ASSERT(window->ParentWindowForFocusRoute != 0); // Invalid value for FocusRouteParentWindowId.
8731 }
8732
8733 // Inherit SetWindowFontScale() from parent until we fix this system...
8734 window->FontWindowScaleParents =
8735 parent_window ? parent_window->FontWindowScaleParents * parent_window->FontWindowScale : 1.0f;
8736 }
8737
8738 // Add to focus scope stack
8739 PushFocusScope((window->ChildFlags & ImGuiChildFlags_NavFlattened) ? g.CurrentFocusScopeId : window->ID);
8740 window->NavRootFocusScopeId = g.CurrentFocusScopeId;
8741
8742 // Add to popup stacks: update OpenPopupStack[] data, push to BeginPopupStack[]
8743 if (flags & ImGuiWindowFlags_Popup)
8744 {
8745 ImGuiPopupData &popup_ref = g.OpenPopupStack[g.BeginPopupStack.Size];
8746 popup_ref.Window = window;
8747 popup_ref.ParentNavLayer = parent_window_in_stack->DC.NavLayerCurrent;
8748 g.BeginPopupStack.push_back(popup_ref);
8749 window->PopupId = popup_ref.PopupId;
8750 }
8751
8752 // Process SetNextWindow***() calls
8753 // (FIXME: Consider splitting the HasXXX flags into X/Y components
8754 bool window_pos_set_by_api = false;
8755 bool window_size_x_set_by_api = false, window_size_y_set_by_api = false;
8756 if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasPos)
8757 {
8758 window_pos_set_by_api = (window->SetWindowPosAllowFlags & g.NextWindowData.PosCond) != 0;
8759 if (window_pos_set_by_api && ImLengthSqr(g.NextWindowData.PosPivotVal) > 0.00001f)
8760 {
8761 // May be processed on the next frame if this is our first frame and we are measuring size
8762 // FIXME: Look into removing the branch so everything can go through this same code path for consistency.
8763 window->SetWindowPosVal = g.NextWindowData.PosVal;
8764 window->SetWindowPosPivot = g.NextWindowData.PosPivotVal;
8765 window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
8766 }
8767 else
8768 {
8769 SetWindowPos(window, g.NextWindowData.PosVal, g.NextWindowData.PosCond);
8770 }
8771 }
8772 if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSize)
8773 {
8774 window_size_x_set_by_api =
8775 (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.x > 0.0f);
8776 window_size_y_set_by_api =
8777 (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.y > 0.0f);
8778 if ((window->ChildFlags & ImGuiChildFlags_ResizeX) &&
8779 (window->SetWindowSizeAllowFlags & ImGuiCond_FirstUseEver) ==
8780 0) // Axis-specific conditions for BeginChild()
8781 g.NextWindowData.SizeVal.x = window->SizeFull.x;
8782 if ((window->ChildFlags & ImGuiChildFlags_ResizeY) &&
8783 (window->SetWindowSizeAllowFlags & ImGuiCond_FirstUseEver) == 0)
8784 g.NextWindowData.SizeVal.y = window->SizeFull.y;
8785 SetWindowSize(window, g.NextWindowData.SizeVal, g.NextWindowData.SizeCond);
8786 }
8787 if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasScroll)
8788 {
8789 if (g.NextWindowData.ScrollVal.x >= 0.0f)
8790 {
8791 window->ScrollTarget.x = g.NextWindowData.ScrollVal.x;
8792 window->ScrollTargetCenterRatio.x = 0.0f;
8793 }
8794 if (g.NextWindowData.ScrollVal.y >= 0.0f)
8795 {
8796 window->ScrollTarget.y = g.NextWindowData.ScrollVal.y;
8797 window->ScrollTargetCenterRatio.y = 0.0f;
8798 }
8799 }
8800 if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasContentSize)
8801 window->ContentSizeExplicit = g.NextWindowData.ContentSizeVal;
8802 else if (first_begin_of_the_frame)
8803 window->ContentSizeExplicit = ImVec2(0.0f, 0.0f);
8804 if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasWindowClass)
8805 window->WindowClass = g.NextWindowData.WindowClass;
8806 if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasCollapsed)
8807 SetWindowCollapsed(window, g.NextWindowData.CollapsedVal, g.NextWindowData.CollapsedCond);
8808 if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasFocus)
8809 FocusWindow(window);
8810 if (window->Appearing)
8811 SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, false);
8812
8813 // [EXPERIMENTAL] Skip Refresh mode
8814 UpdateWindowSkipRefresh(window);
8815
8816 // Nested root windows (typically tooltips) override disabled state
8817 if (window_stack_data.DisabledOverrideReenable && window->RootWindow == window)
8818 BeginDisabledOverrideReenable();
8819
8820 // We intentionally set g.CurrentWindow to NULL to prevent usage until when the viewport is set, then will call
8821 // SetCurrentWindow()
8822 g.CurrentWindow = NULL;
8823
8824 // When reusing window again multiple times a frame, just append content (don't need to setup again)
8825 if (first_begin_of_the_frame && !window->SkipRefresh)
8826 {
8827 // Initialize
8828 const bool window_is_child_tooltip =
8829 (flags & ImGuiWindowFlags_ChildWindow) &&
8830 (flags &
8831 ImGuiWindowFlags_Tooltip); // FIXME-WIP: Undocumented behavior of Child+Tooltip for pinned tooltip (#1345)
8832 const bool window_just_appearing_after_hidden_for_resize = (window->HiddenFramesCannotSkipItems > 0);
8833 window->Active = true;
8834 window->HasCloseButton = (p_open != NULL);
8835 window->ClipRect = ImVec4(-FLT_MAX, -FLT_MAX, +FLT_MAX, +FLT_MAX);
8836 window->IDStack.resize(1);
8837 window->DrawList->_ResetForNewFrame();
8838 window->DC.CurrentTableIdx = -1;
8839 if (flags & ImGuiWindowFlags_DockNodeHost)
8840 {
8841 window->DrawList->ChannelsSplit(2);
8842 window->DrawList->ChannelsSetCurrent(
8843 DOCKING_HOST_DRAW_CHANNEL_FG); // Render decorations on channel 1 as we will render the backgrounds
8844 // manually later
8845 }
8846
8847 // Restore buffer capacity when woken from a compacted state, to avoid
8848 if (window->MemoryCompacted)
8849 GcAwakeTransientWindowBuffers(window);
8850
8851 // Update stored window name when it changes (which can _only_ happen with the "###" operator, so the ID would
8852 // stay unchanged). The title bar always display the 'name' parameter, so we only update the string storage if
8853 // it needs to be visible to the end-user elsewhere.
8854 bool window_title_visible_elsewhere = false;
8855 if ((window->Viewport && window->Viewport->Window == window) || (window->DockIsActive))
8856 window_title_visible_elsewhere = true;
8857 else if (g.NavWindowingListWindow != NULL &&
8858 (flags & ImGuiWindowFlags_NoNavFocus) == 0) // Window titles visible when using CTRL+TAB
8859 window_title_visible_elsewhere = true;
8860 else if (flags & ImGuiWindowFlags_ChildMenu)
8861 window_title_visible_elsewhere = true;
8862 if (window_title_visible_elsewhere && !window_just_created && strcmp(name, window->Name) != 0)
8863 {
8864 size_t buf_len = (size_t)window->NameBufLen;
8865 window->Name = ImStrdupcpy(window->Name, &buf_len, name);
8866 window->NameBufLen = (int)buf_len;
8867 }
8868
8869 // UPDATE CONTENTS SIZE, UPDATE HIDDEN STATUS
8870
8871 // Update contents size from last frame for auto-fitting (or use explicit size)
8872 CalcWindowContentSizes(window, &window->ContentSize, &window->ContentSizeIdeal);
8873
8874 // FIXME: These flags are decremented before they are used. This means that in order to have these fields
8875 // produce their intended behaviors for one frame we must set them to at least 2, which is counter-intuitive.
8876 // HiddenFramesCannotSkipItems is a more complicated case because it has a single usage before this code block
8877 // and may be set below before it is finally checked.
8878 if (window->HiddenFramesCanSkipItems > 0)
8879 window->HiddenFramesCanSkipItems--;
8880 if (window->HiddenFramesCannotSkipItems > 0)
8881 window->HiddenFramesCannotSkipItems--;
8882 if (window->HiddenFramesForRenderOnly > 0)
8883 window->HiddenFramesForRenderOnly--;
8884
8885 // Hide new windows for one frame until they calculate their size
8886 if (window_just_created && (!window_size_x_set_by_api || !window_size_y_set_by_api))
8887 window->HiddenFramesCannotSkipItems = 1;
8888
8889 // Hide popup/tooltip window when re-opening while we measure size (because we recycle the windows)
8890 // We reset Size/ContentSize for reappearing popups/tooltips early in this function, so further code won't be
8891 // tempted to use the old size.
8892 if (window_just_activated_by_user && (flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) != 0)
8893 {
8894 window->HiddenFramesCannotSkipItems = 1;
8895 if (flags & ImGuiWindowFlags_AlwaysAutoResize)
8896 {
8897 if (!window_size_x_set_by_api)
8898 window->Size.x = window->SizeFull.x = 0.f;
8899 if (!window_size_y_set_by_api)
8900 window->Size.y = window->SizeFull.y = 0.f;
8901 window->ContentSize = window->ContentSizeIdeal = ImVec2(0.f, 0.f);
8902 }
8903 }
8904
8905 // SELECT VIEWPORT
8906 // We need to do this before using any style/font sizes, as viewport with a different DPI may affect font sizes.
8907
8908 WindowSelectViewport(window);
8909 SetCurrentViewport(window, window->Viewport);
8910 window->FontDpiScale =
8911 (g.IO.ConfigFlags & ImGuiConfigFlags_DpiEnableScaleFonts) ? window->Viewport->DpiScale : 1.0f;
8912 SetCurrentWindow(window);
8913 flags = window->Flags;
8914
8915 // LOCK BORDER SIZE AND PADDING FOR THE FRAME (so that altering them doesn't cause inconsistencies)
8916 // We read Style data after the call to UpdateSelectWindowViewport() which might be swapping the style.
8917
8918 if (!window->DockIsActive && (flags & ImGuiWindowFlags_ChildWindow))
8919 window->WindowBorderSize = style.ChildBorderSize;
8920 else
8921 window->WindowBorderSize =
8922 ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) && !(flags & ImGuiWindowFlags_Modal))
8923 ? style.PopupBorderSize
8924 : style.WindowBorderSize;
8925 window->WindowPadding = style.WindowPadding;
8926 if (!window->DockIsActive && (flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) &&
8927 !(window->ChildFlags & ImGuiChildFlags_AlwaysUseWindowPadding) && window->WindowBorderSize == 0.0f)
8928 window->WindowPadding = ImVec2(0.0f, (flags & ImGuiWindowFlags_MenuBar) ? style.WindowPadding.y : 0.0f);
8929
8930 // Lock menu offset so size calculation can use it as menu-bar windows need a minimum size.
8931 window->DC.MenuBarOffset.x =
8932 ImMax(ImMax(window->WindowPadding.x, style.ItemSpacing.x), g.NextWindowData.MenuBarOffsetMinVal.x);
8933 window->DC.MenuBarOffset.y = g.NextWindowData.MenuBarOffsetMinVal.y;
8934 window->TitleBarHeight =
8935 (flags & ImGuiWindowFlags_NoTitleBar) ? 0.0f : g.FontSize + g.Style.FramePadding.y * 2.0f;
8936 window->MenuBarHeight = (flags & ImGuiWindowFlags_MenuBar)
8937 ? window->DC.MenuBarOffset.y + g.FontSize + g.Style.FramePadding.y * 2.0f
8938 : 0.0f;
8939 window->FontRefSize =
8940 g.FontSize; // Lock this to discourage calling window->CalcFontSize() outside of current window.
8941
8942 // Depending on condition we use previous or current window size to compare against contents size to decide if a
8943 // scrollbar should be visible. Those flags will be altered further down in the function depending on more
8944 // conditions.
8945 bool use_current_size_for_scrollbar_x = window_just_created;
8946 bool use_current_size_for_scrollbar_y = window_just_created;
8947 if (window_size_x_set_by_api && window->ContentSizeExplicit.x != 0.0f)
8948 use_current_size_for_scrollbar_x = true;
8949 if (window_size_y_set_by_api && window->ContentSizeExplicit.y != 0.0f) // #7252
8950 use_current_size_for_scrollbar_y = true;
8951
8952 // Collapse window by double-clicking on title bar
8953 // At this point we don't have a clipping rectangle setup yet, so we can use the title bar area for hit
8954 // detection and drawing
8955 if (!(flags & ImGuiWindowFlags_NoTitleBar) && !(flags & ImGuiWindowFlags_NoCollapse) && !window->DockIsActive)
8956 {
8957 // We don't use a regular button+id to test for double-click on title bar (mostly due to legacy reason,
8958 // could be fixed), so verify that we don't have items over the title bar.
8959 ImRect title_bar_rect = window->TitleBarRect();
8960 if (g.HoveredWindow == window && g.HoveredId == 0 && g.HoveredIdPreviousFrame == 0 && g.ActiveId == 0 &&
8961 IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max))
8962 if (g.IO.MouseClickedCount[0] == 2 && GetKeyOwner(ImGuiKey_MouseLeft) == ImGuiKeyOwner_NoOwner)
8963 window->WantCollapseToggle = true;
8964 if (window->WantCollapseToggle)
8965 {
8966 window->Collapsed = !window->Collapsed;
8967 if (!window->Collapsed)
8968 use_current_size_for_scrollbar_y = true;
8969 MarkIniSettingsDirty(window);
8970 }
8971 }
8972 else
8973 {
8974 window->Collapsed = false;
8975 }
8976 window->WantCollapseToggle = false;
8977
8978 // SIZE
8979
8980 // Outer Decoration Sizes
8981 // (we need to clear ScrollbarSize immediately as CalcWindowAutoFitSize() needs it and can be called from other
8982 // locations).
8983 const ImVec2 scrollbar_sizes_from_last_frame = window->ScrollbarSizes;
8984 window->DecoOuterSizeX1 = 0.0f;
8985 window->DecoOuterSizeX2 = 0.0f;
8986 window->DecoOuterSizeY1 = window->TitleBarHeight + window->MenuBarHeight;
8987 window->DecoOuterSizeY2 = 0.0f;
8988 window->ScrollbarSizes = ImVec2(0.0f, 0.0f);
8989
8990 // Calculate auto-fit size, handle automatic resize
8991 const ImVec2 size_auto_fit = CalcWindowAutoFitSize(window, window->ContentSizeIdeal);
8992 if ((flags & ImGuiWindowFlags_AlwaysAutoResize) && !window->Collapsed)
8993 {
8994 // Using SetNextWindowSize() overrides ImGuiWindowFlags_AlwaysAutoResize, so it can be used on
8995 // tooltips/popups, etc.
8996 if (!window_size_x_set_by_api)
8997 {
8998 window->SizeFull.x = size_auto_fit.x;
8999 use_current_size_for_scrollbar_x = true;
9000 }
9001 if (!window_size_y_set_by_api)
9002 {
9003 window->SizeFull.y = size_auto_fit.y;
9004 use_current_size_for_scrollbar_y = true;
9005 }
9006 }
9007 else if (window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0)
9008 {
9009 // Auto-fit may only grow window during the first few frames
9010 // We still process initial auto-fit on collapsed windows to get a window width, but otherwise don't honor
9011 // ImGuiWindowFlags_AlwaysAutoResize when collapsed.
9012 if (!window_size_x_set_by_api && window->AutoFitFramesX > 0)
9013 {
9014 window->SizeFull.x =
9015 window->AutoFitOnlyGrows ? ImMax(window->SizeFull.x, size_auto_fit.x) : size_auto_fit.x;
9016 use_current_size_for_scrollbar_x = true;
9017 }
9018 if (!window_size_y_set_by_api && window->AutoFitFramesY > 0)
9019 {
9020 window->SizeFull.y =
9021 window->AutoFitOnlyGrows ? ImMax(window->SizeFull.y, size_auto_fit.y) : size_auto_fit.y;
9022 use_current_size_for_scrollbar_y = true;
9023 }
9024 if (!window->Collapsed)
9025 MarkIniSettingsDirty(window);
9026 }
9027
9028 // Apply minimum/maximum window size constraints and final size
9029 window->SizeFull = CalcWindowSizeAfterConstraint(window, window->SizeFull);
9030 window->Size = window->Collapsed && !(flags & ImGuiWindowFlags_ChildWindow) ? window->TitleBarRect().GetSize()
9031 : window->SizeFull;
9032
9033 // POSITION
9034
9035 // Popup latch its initial position, will position itself when it appears next frame
9036 if (window_just_activated_by_user)
9037 {
9038 window->AutoPosLastDirection = ImGuiDir_None;
9039 if ((flags & ImGuiWindowFlags_Popup) != 0 && !(flags & ImGuiWindowFlags_Modal) &&
9040 !window_pos_set_by_api) // FIXME: BeginPopup() could use SetNextWindowPos()
9041 window->Pos = g.BeginPopupStack.back().OpenPopupPos;
9042 }
9043
9044 // Position child window
9045 if (flags & ImGuiWindowFlags_ChildWindow)
9046 {
9047 IM_ASSERT(parent_window && parent_window->Active);
9048 window->BeginOrderWithinParent = (short)parent_window->DC.ChildWindows.Size;
9049 parent_window->DC.ChildWindows.push_back(window);
9050 if (!(flags & ImGuiWindowFlags_Popup) && !window_pos_set_by_api && !window_is_child_tooltip)
9051 window->Pos = parent_window->DC.CursorPos;
9052 }
9053
9054 const bool window_pos_with_pivot =
9055 (window->SetWindowPosVal.x != FLT_MAX && window->HiddenFramesCannotSkipItems == 0);
9056 if (window_pos_with_pivot)
9057 SetWindowPos(window, window->SetWindowPosVal - window->Size * window->SetWindowPosPivot,
9058 0); // Position given a pivot (e.g. for centering)
9059 else if ((flags & ImGuiWindowFlags_ChildMenu) != 0)
9060 window->Pos = FindBestWindowPosForPopup(window);
9061 else if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api &&
9062 window_just_appearing_after_hidden_for_resize)
9063 window->Pos = FindBestWindowPosForPopup(window);
9064 else if ((flags & ImGuiWindowFlags_Tooltip) != 0 && !window_pos_set_by_api && !window_is_child_tooltip)
9065 window->Pos = FindBestWindowPosForPopup(window);
9066
9067 // Late create viewport if we don't fit within our current host viewport.
9068 if (window->ViewportAllowPlatformMonitorExtend >= 0 && !window->ViewportOwned &&
9069 !(window->Viewport->Flags & ImGuiViewportFlags_IsMinimized))
9070 if (!window->Viewport->GetMainRect().Contains(window->Rect()))
9071 {
9072 // This is based on the assumption that the DPI will be known ahead (same as the DPI of the selection
9073 // done in UpdateSelectWindowViewport)
9074 // ImGuiViewport* old_viewport = window->Viewport;
9075 window->Viewport = AddUpdateViewport(window, window->ID, window->Pos, window->Size,
9076 ImGuiViewportFlags_NoFocusOnAppearing);
9077
9078 // FIXME-DPI
9079 // IM_ASSERT(old_viewport->DpiScale == window->Viewport->DpiScale); // FIXME-DPI: Something went wrong
9080 SetCurrentViewport(window, window->Viewport);
9081 window->FontDpiScale =
9082 (g.IO.ConfigFlags & ImGuiConfigFlags_DpiEnableScaleFonts) ? window->Viewport->DpiScale : 1.0f;
9083 SetCurrentWindow(window);
9084 }
9085
9086 if (window->ViewportOwned)
9087 WindowSyncOwnedViewport(window, parent_window_in_stack);
9088
9089 // Calculate the range of allowed position for that window (to be movable and visible past safe area padding)
9090 // When clamping to stay visible, we will enforce that window->Pos stays inside of visibility_rect.
9091 ImRect viewport_rect(window->Viewport->GetMainRect());
9092 ImRect viewport_work_rect(window->Viewport->GetWorkRect());
9093 ImVec2 visibility_padding = ImMax(style.DisplayWindowPadding, style.DisplaySafeAreaPadding);
9094 ImRect visibility_rect(viewport_work_rect.Min + visibility_padding,
9095 viewport_work_rect.Max - visibility_padding);
9096
9097 // Clamp position/size so window stays visible within its viewport or monitor
9098 // Ignore zero-sized display explicitly to avoid losing positions if a window manager reports zero-sized window
9099 // when initializing or minimizing.
9100 // FIXME: Similar to code in GetWindowAllowedExtentRect()
9101 if (!window_pos_set_by_api && !(flags & ImGuiWindowFlags_ChildWindow))
9102 {
9103 if (!window->ViewportOwned && viewport_rect.GetWidth() > 0 && viewport_rect.GetHeight() > 0.0f)
9104 {
9105 ClampWindowPos(window, visibility_rect);
9106 }
9107 else if (window->ViewportOwned && g.PlatformIO.Monitors.Size > 0)
9108 {
9109 if (g.MovingWindow != NULL && window->RootWindowDockTree == g.MovingWindow->RootWindowDockTree)
9110 {
9111 // While moving windows we allow them to straddle monitors (#7299, #3071)
9112 visibility_rect = g.PlatformMonitorsFullWorkRect;
9113 }
9114 else
9115 {
9116 // When not moving ensure visible in its monitor
9117 // Lost windows (e.g. a monitor disconnected) will naturally moved to the fallback/dummy monitor aka
9118 // the main viewport.
9119 const ImGuiPlatformMonitor *monitor = GetViewportPlatformMonitor(window->Viewport);
9120 visibility_rect = ImRect(monitor->WorkPos, monitor->WorkPos + monitor->WorkSize);
9121 }
9122 visibility_rect.Expand(-visibility_padding);
9123 ClampWindowPos(window, visibility_rect);
9124 }
9125 }
9126 window->Pos = ImTrunc(window->Pos);
9127
9128 // Lock window rounding for the frame (so that altering them doesn't cause inconsistencies)
9129 // Large values tend to lead to variety of artifacts and are not recommended.
9130 if (window->ViewportOwned || window->DockIsActive)
9131 window->WindowRounding = 0.0f;
9132 else
9133 window->WindowRounding = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildRounding
9134 : ((flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiWindowFlags_Modal))
9135 ? style.PopupRounding
9136 : style.WindowRounding;
9137
9138 // For windows with title bar or menu bar, we clamp to FrameHeight(FontSize + FramePadding.y * 2.0f) to
9139 // completely hide artifacts.
9140 // if ((window->Flags & ImGuiWindowFlags_MenuBar) || !(window->Flags & ImGuiWindowFlags_NoTitleBar))
9141 // window->WindowRounding = ImMin(window->WindowRounding, g.FontSize + style.FramePadding.y * 2.0f);
9142
9143 // Apply window focus (new and reactivated windows are moved to front)
9144 bool want_focus = false;
9145 if (window_just_activated_by_user && !(flags & ImGuiWindowFlags_NoFocusOnAppearing))
9146 {
9147 if (flags & ImGuiWindowFlags_Popup)
9148 want_focus = true;
9149 else if ((window->DockIsActive || (flags & ImGuiWindowFlags_ChildWindow) == 0) &&
9150 !(flags & ImGuiWindowFlags_Tooltip))
9151 want_focus = true;
9152 }
9153
9154 // [Test Engine] Register whole window in the item system (before submitting further decorations)
9155#ifdef IMGUI_ENABLE_TEST_ENGINE
9156 if (g.TestEngineHookItems)
9157 {
9158 IM_ASSERT(window->IDStack.Size == 1);
9159 window->IDStack.Size = 0; // As window->IDStack[0] == window->ID here, make sure TestEngine doesn't
9160 // erroneously see window as parent of itself.
9161 window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;
9162 IMGUI_TEST_ENGINE_ITEM_ADD(window->ID, window->Rect(), NULL);
9163 IMGUI_TEST_ENGINE_ITEM_INFO(window->ID, window->Name,
9164 (g.HoveredWindow == window) ? ImGuiItemStatusFlags_HoveredRect : 0);
9165 window->IDStack.Size = 1;
9166 window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
9167 }
9168#endif
9169
9170 // Decide if we are going to handle borders and resize grips
9171 const bool handle_borders_and_resize_grips = (window->DockNodeAsHost || !window->DockIsActive);
9172
9173 // Handle manual resize: Resize Grips, Borders, Gamepad
9174 int border_hovered = -1, border_held = -1;
9175 ImU32 resize_grip_col[4] = {};
9176 const int resize_grip_count =
9177 ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup)) ? 0
9178 : g.IO.ConfigWindowsResizeFromEdges
9179 ? 2
9180 : 1; // Allow resize from lower-left if we have the mouse cursor feedback for it.
9181 const float resize_grip_draw_size =
9182 IM_TRUNC(ImMax(g.FontSize * 1.10f, window->WindowRounding + 1.0f + g.FontSize * 0.2f));
9183 if (handle_borders_and_resize_grips && !window->Collapsed)
9184 if (int auto_fit_mask = UpdateWindowManualResize(window, size_auto_fit, &border_hovered, &border_held,
9185 resize_grip_count, &resize_grip_col[0], visibility_rect))
9186 {
9187 if (auto_fit_mask & (1 << ImGuiAxis_X))
9188 use_current_size_for_scrollbar_x = true;
9189 if (auto_fit_mask & (1 << ImGuiAxis_Y))
9190 use_current_size_for_scrollbar_y = true;
9191 }
9192 window->ResizeBorderHovered = (signed char)border_hovered;
9193 window->ResizeBorderHeld = (signed char)border_held;
9194
9195 // Synchronize window --> viewport again and one last time (clamping and manual resize may have affected either)
9196 if (window->ViewportOwned)
9197 {
9198 if (!window->Viewport->PlatformRequestMove)
9199 window->Viewport->Pos = window->Pos;
9200 if (!window->Viewport->PlatformRequestResize)
9201 window->Viewport->Size = window->Size;
9202 window->Viewport->UpdateWorkRect();
9203 viewport_rect = window->Viewport->GetMainRect();
9204 }
9205
9206 // Save last known viewport position within the window itself (so it can be saved in .ini file and restored)
9207 window->ViewportPos = window->Viewport->Pos;
9208
9209 // SCROLLBAR VISIBILITY
9210
9211 // Update scrollbar visibility (based on the Size that was effective during last frame or the auto-resized
9212 // Size).
9213 if (!window->Collapsed)
9214 {
9215 // When reading the current size we need to read it after size constraints have been applied.
9216 // Intentionally use previous frame values for InnerRect and ScrollbarSizes.
9217 // And when we use window->DecorationUp here it doesn't have ScrollbarSizes.y applied yet.
9218 ImVec2 avail_size_from_current_frame =
9219 ImVec2(window->SizeFull.x, window->SizeFull.y - (window->DecoOuterSizeY1 + window->DecoOuterSizeY2));
9220 ImVec2 avail_size_from_last_frame = window->InnerRect.GetSize() + scrollbar_sizes_from_last_frame;
9221 ImVec2 needed_size_from_last_frame =
9222 window_just_created ? ImVec2(0, 0) : window->ContentSize + window->WindowPadding * 2.0f;
9223 float size_x_for_scrollbars =
9224 use_current_size_for_scrollbar_x ? avail_size_from_current_frame.x : avail_size_from_last_frame.x;
9225 float size_y_for_scrollbars =
9226 use_current_size_for_scrollbar_y ? avail_size_from_current_frame.y : avail_size_from_last_frame.y;
9227 bool scrollbar_x_prev = window->ScrollbarX;
9228 // bool scrollbar_y_from_last_frame = window->ScrollbarY; // FIXME: May want to use that in the ScrollbarX
9229 // expression? How many pros vs cons?
9230 window->ScrollbarY =
9231 (flags & ImGuiWindowFlags_AlwaysVerticalScrollbar) ||
9232 ((needed_size_from_last_frame.y > size_y_for_scrollbars) && !(flags & ImGuiWindowFlags_NoScrollbar));
9233 window->ScrollbarX =
9234 (flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar) ||
9235 ((needed_size_from_last_frame.x >
9236 size_x_for_scrollbars - (window->ScrollbarY ? style.ScrollbarSize : 0.0f)) &&
9237 !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar));
9238
9239 // Track when ScrollbarX visibility keeps toggling, which is a sign of a feedback loop, and stabilize by
9240 // enforcing visibility (#3285, #8488) (Feedback loops of this sort can manifest in various situations, but
9241 // combining horizontal + vertical scrollbar + using a clipper with varying width items is one frequent
9242 // cause.
9243 // The better solution is to, either (1) enforce visibility by using
9244 // ImGuiWindowFlags_AlwaysHorizontalScrollbar or (2) declare stable contents width with
9245 // SetNextWindowContentSize(), if you can compute it)
9246 window->ScrollbarXStabilizeToggledHistory <<= 1;
9247 window->ScrollbarXStabilizeToggledHistory |= (scrollbar_x_prev != window->ScrollbarX) ? 0x01 : 0x00;
9248 const bool scrollbar_x_stabilize =
9249 (window->ScrollbarXStabilizeToggledHistory != 0) &&
9250 ImCountSetBits(window->ScrollbarXStabilizeToggledHistory) >= 4; // 4 == half of bits in our U8 history.
9251 if (scrollbar_x_stabilize)
9252 window->ScrollbarX = true;
9253 // if (scrollbar_x_stabilize && !window->ScrollbarXStabilizeEnabled)
9254 // IMGUI_DEBUG_LOG("[scroll] Stabilize ScrollbarX for Window '%s'\n", window->Name);
9255 window->ScrollbarXStabilizeEnabled = scrollbar_x_stabilize;
9256
9257 if (window->ScrollbarX && !window->ScrollbarY)
9258 window->ScrollbarY = (needed_size_from_last_frame.y > size_y_for_scrollbars - style.ScrollbarSize) &&
9259 !(flags & ImGuiWindowFlags_NoScrollbar);
9260 window->ScrollbarSizes = ImVec2(window->ScrollbarY ? style.ScrollbarSize : 0.0f,
9261 window->ScrollbarX ? style.ScrollbarSize : 0.0f);
9262
9263 // Amend the partially filled window->DecorationXXX values.
9264 window->DecoOuterSizeX2 += window->ScrollbarSizes.x;
9265 window->DecoOuterSizeY2 += window->ScrollbarSizes.y;
9266 }
9267
9268 // UPDATE RECTANGLES (1- THOSE NOT AFFECTED BY SCROLLING)
9269 // Update various regions. Variables they depend on should be set above in this function.
9270 // We set this up after processing the resize grip so that our rectangles doesn't lag by a frame.
9271
9272 // Outer rectangle
9273 // Not affected by window border size. Used by:
9274 // - FindHoveredWindow() (w/ extra padding when border resize is enabled)
9275 // - Begin() initial clipping rect for drawing window background and borders.
9276 // - Begin() clipping whole child
9277 const ImRect host_rect =
9278 ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !window_is_child_tooltip)
9279 ? parent_window->ClipRect
9280 : viewport_rect;
9281 const ImRect outer_rect = window->Rect();
9282 const ImRect title_bar_rect = window->TitleBarRect();
9283 window->OuterRectClipped = outer_rect;
9284 if (window->DockIsActive)
9285 window->OuterRectClipped.Min.y += window->TitleBarHeight;
9286 window->OuterRectClipped.ClipWith(host_rect);
9287
9288 // Inner rectangle
9289 // Not affected by window border size. Used by:
9290 // - InnerClipRect
9291 // - ScrollToRectEx()
9292 // - NavUpdatePageUpPageDown()
9293 // - Scrollbar()
9294 window->InnerRect.Min.x = window->Pos.x + window->DecoOuterSizeX1;
9295 window->InnerRect.Min.y = window->Pos.y + window->DecoOuterSizeY1;
9296 window->InnerRect.Max.x = window->Pos.x + window->Size.x - window->DecoOuterSizeX2;
9297 window->InnerRect.Max.y = window->Pos.y + window->Size.y - window->DecoOuterSizeY2;
9298
9299 // Inner clipping rectangle.
9300 // - Extend a outside of normal work region up to borders.
9301 // - This is to allow e.g. Selectable or CollapsingHeader or some separators to cover that space.
9302 // - It also makes clipped items be more noticeable.
9303 // - And is consistent on both axis (prior to 2024/05/03 ClipRect used WindowPadding.x * 0.5f on left and right
9304 // edge), see #3312
9305 // - Force round operator last to ensure that e.g. (int)(max.x-min.x) in user's render code produce correct
9306 // result. Note that if our window is collapsed we will end up with an inverted (~null) clipping rectangle which
9307 // is the correct behavior. Affected by window/frame border size. Used by:
9308 // - Begin() initial clip rect
9309 float top_border_size =
9310 (((flags & ImGuiWindowFlags_MenuBar) || !(flags & ImGuiWindowFlags_NoTitleBar)) ? style.FrameBorderSize
9311 : window->WindowBorderSize);
9312
9313 // Try to match the fact that our border is drawn centered over the window rectangle, rather than inner.
9314 // This is why we do a *0.5f here. We don't currently even technically support large values for
9315 // WindowBorderSize, see e.g #7887 #7888, but may do after we move the window border to become an inner border
9316 // (and then we can remove the 0.5f here).
9317 window->InnerClipRect.Min.x = ImFloor(0.5f + window->InnerRect.Min.x + window->WindowBorderSize * 0.5f);
9318 window->InnerClipRect.Min.y = ImFloor(0.5f + window->InnerRect.Min.y + top_border_size * 0.5f);
9319 window->InnerClipRect.Max.x = ImFloor(window->InnerRect.Max.x - window->WindowBorderSize * 0.5f);
9320 window->InnerClipRect.Max.y = ImFloor(window->InnerRect.Max.y - window->WindowBorderSize * 0.5f);
9321 window->InnerClipRect.ClipWithFull(host_rect);
9322
9323 // Default item width. Make it proportional to window size if window manually resizes
9324 if (window->Size.x > 0.0f && !(flags & ImGuiWindowFlags_Tooltip) &&
9325 !(flags & ImGuiWindowFlags_AlwaysAutoResize))
9326 window->ItemWidthDefault = ImTrunc(window->Size.x * 0.65f);
9327 else
9328 window->ItemWidthDefault = ImTrunc(g.FontSize * 16.0f);
9329
9330 // SCROLLING
9331
9332 // Lock down maximum scrolling
9333 // The value of ScrollMax are ahead from ScrollbarX/ScrollbarY which is intentionally using InnerRect from
9334 // previous rect in order to accommodate for right/bottom aligned items without creating a scrollbar.
9335 window->ScrollMax.x =
9336 ImMax(0.0f, window->ContentSize.x + window->WindowPadding.x * 2.0f - window->InnerRect.GetWidth());
9337 window->ScrollMax.y =
9338 ImMax(0.0f, window->ContentSize.y + window->WindowPadding.y * 2.0f - window->InnerRect.GetHeight());
9339
9340 // Apply scrolling
9341 window->Scroll = CalcNextScrollFromScrollTargetAndClamp(window);
9342 window->ScrollTarget = ImVec2(FLT_MAX, FLT_MAX);
9343 window->DecoInnerSizeX1 = window->DecoInnerSizeY1 = 0.0f;
9344
9345 // DRAWING
9346
9347 // Setup draw list and outer clipping rectangle
9348 IM_ASSERT(window->DrawList->CmdBuffer.Size == 1 && window->DrawList->CmdBuffer[0].ElemCount == 0);
9349 window->DrawList->PushTextureID(g.Font->ContainerAtlas->TexID);
9350 PushClipRect(host_rect.Min, host_rect.Max, false);
9351
9352 // Child windows can render their decoration (bg color, border, scrollbars, etc.) within their parent to save a
9353 // draw call (since 1.71) When using overlapping child windows, this will break the assumption that child
9354 // z-order is mapped to submission order.
9355 // FIXME: User code may rely on explicit sorting of overlapping child window and would need to disable this
9356 // somehow. Please get in contact if you are affected (github #4493)
9357 const bool is_undocked_or_docked_visible = !window->DockIsActive || window->DockTabIsVisible;
9358 if (is_undocked_or_docked_visible)
9359 {
9360 bool render_decorations_in_parent = false;
9361 if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !window_is_child_tooltip)
9362 {
9363 // - We test overlap with the previous child window only (testing all would end up being O(log N) not a
9364 // good investment here)
9365 // - We disable this when the parent window has zero vertices, which is a common pattern leading to
9366 // laying out multiple overlapping childs
9367 ImGuiWindow *previous_child =
9368 parent_window->DC.ChildWindows.Size >= 2
9369 ? parent_window->DC.ChildWindows[parent_window->DC.ChildWindows.Size - 2]
9370 : NULL;
9371 bool previous_child_overlapping =
9372 previous_child ? previous_child->Rect().Overlaps(window->Rect()) : false;
9373 bool parent_is_empty = (parent_window->DrawList->VtxBuffer.Size == 0);
9374 if (window->DrawList->CmdBuffer.back().ElemCount == 0 && !parent_is_empty &&
9375 !previous_child_overlapping)
9376 render_decorations_in_parent = true;
9377 }
9378 if (render_decorations_in_parent)
9379 window->DrawList = parent_window->DrawList;
9380
9381 // Handle title bar, scrollbar, resize grips and resize borders
9382 const ImGuiWindow *window_to_highlight = g.NavWindowingTarget ? g.NavWindowingTarget : g.NavWindow;
9383 const bool title_bar_is_highlight =
9384 want_focus ||
9385 (window_to_highlight &&
9386 (window->RootWindowForTitleBarHighlight == window_to_highlight->RootWindowForTitleBarHighlight ||
9387 (window->DockNode && window->DockNode == window_to_highlight->DockNode)));
9388 RenderWindowDecorations(window, title_bar_rect, title_bar_is_highlight, handle_borders_and_resize_grips,
9389 resize_grip_count, resize_grip_col, resize_grip_draw_size);
9390
9391 if (render_decorations_in_parent)
9392 window->DrawList = &window->DrawListInst;
9393 }
9394
9395 // UPDATE RECTANGLES (2- THOSE AFFECTED BY SCROLLING)
9396
9397 // Work rectangle.
9398 // Affected by window padding and border size. Used by:
9399 // - Columns() for right-most edge
9400 // - TreeNode(), CollapsingHeader() for right-most edge
9401 // - BeginTabBar() for right-most edge
9402 const bool allow_scrollbar_x =
9403 !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar);
9404 const bool allow_scrollbar_y = !(flags & ImGuiWindowFlags_NoScrollbar);
9405 const float work_rect_size_x =
9406 (window->ContentSizeExplicit.x != 0.0f ? window->ContentSizeExplicit.x
9407 : ImMax(allow_scrollbar_x ? window->ContentSize.x : 0.0f,
9408 window->Size.x - window->WindowPadding.x * 2.0f -
9409 (window->DecoOuterSizeX1 + window->DecoOuterSizeX2)));
9410 const float work_rect_size_y =
9411 (window->ContentSizeExplicit.y != 0.0f ? window->ContentSizeExplicit.y
9412 : ImMax(allow_scrollbar_y ? window->ContentSize.y : 0.0f,
9413 window->Size.y - window->WindowPadding.y * 2.0f -
9414 (window->DecoOuterSizeY1 + window->DecoOuterSizeY2)));
9415 window->WorkRect.Min.x = ImTrunc(window->InnerRect.Min.x - window->Scroll.x +
9416 ImMax(window->WindowPadding.x, window->WindowBorderSize));
9417 window->WorkRect.Min.y = ImTrunc(window->InnerRect.Min.y - window->Scroll.y +
9418 ImMax(window->WindowPadding.y, window->WindowBorderSize));
9419 window->WorkRect.Max.x = window->WorkRect.Min.x + work_rect_size_x;
9420 window->WorkRect.Max.y = window->WorkRect.Min.y + work_rect_size_y;
9421 window->ParentWorkRect = window->WorkRect;
9422
9423 // [LEGACY] Content Region
9424 // FIXME-OBSOLETE: window->ContentRegionRect.Max is currently very misleading / partly faulty, but some
9425 // BeginChild() patterns relies on it. Unless explicit content size is specified by user, this currently
9426 // represent the region leading to no scrolling. Used by:
9427 // - Mouse wheel scrolling + many other things
9428 window->ContentRegionRect.Min.x =
9429 window->Pos.x - window->Scroll.x + window->WindowPadding.x + window->DecoOuterSizeX1;
9430 window->ContentRegionRect.Min.y =
9431 window->Pos.y - window->Scroll.y + window->WindowPadding.y + window->DecoOuterSizeY1;
9432 window->ContentRegionRect.Max.x =
9433 window->ContentRegionRect.Min.x + (window->ContentSizeExplicit.x != 0.0f
9434 ? window->ContentSizeExplicit.x
9435 : (window->Size.x - window->WindowPadding.x * 2.0f -
9436 (window->DecoOuterSizeX1 + window->DecoOuterSizeX2)));
9437 window->ContentRegionRect.Max.y =
9438 window->ContentRegionRect.Min.y + (window->ContentSizeExplicit.y != 0.0f
9439 ? window->ContentSizeExplicit.y
9440 : (window->Size.y - window->WindowPadding.y * 2.0f -
9441 (window->DecoOuterSizeY1 + window->DecoOuterSizeY2)));
9442
9443 // Setup drawing context
9444 // (NB: That term "drawing context / DC" lost its meaning a long time ago. Initially was meant to hold transient
9445 // data only. Nowadays difference between window-> and window->DC-> is dubious.)
9446 window->DC.Indent.x = window->DecoOuterSizeX1 + window->WindowPadding.x - window->Scroll.x;
9447 window->DC.GroupOffset.x = 0.0f;
9448 window->DC.ColumnsOffset.x = 0.0f;
9449
9450 // Record the loss of precision of CursorStartPos which can happen due to really large scrolling amount.
9451 // This is used by clipper to compensate and fix the most common use case of large scroll area. Easy and cheap,
9452 // next best thing compared to switching everything to double or ImU64.
9453 double start_pos_highp_x = (double)window->Pos.x + window->WindowPadding.x - (double)window->Scroll.x +
9454 window->DecoOuterSizeX1 + window->DC.ColumnsOffset.x;
9455 double start_pos_highp_y =
9456 (double)window->Pos.y + window->WindowPadding.y - (double)window->Scroll.y + window->DecoOuterSizeY1;
9457 window->DC.CursorStartPos = ImVec2((float)start_pos_highp_x, (float)start_pos_highp_y);
9458 window->DC.CursorStartPosLossyness = ImVec2((float)(start_pos_highp_x - window->DC.CursorStartPos.x),
9459 (float)(start_pos_highp_y - window->DC.CursorStartPos.y));
9460 window->DC.CursorPos = window->DC.CursorStartPos;
9461 window->DC.CursorPosPrevLine = window->DC.CursorPos;
9462 window->DC.CursorMaxPos = window->DC.CursorStartPos;
9463 window->DC.IdealMaxPos = window->DC.CursorStartPos;
9464 window->DC.CurrLineSize = window->DC.PrevLineSize = ImVec2(0.0f, 0.0f);
9465 window->DC.CurrLineTextBaseOffset = window->DC.PrevLineTextBaseOffset = 0.0f;
9466 window->DC.IsSameLine = window->DC.IsSetPos = false;
9467
9468 window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
9469 window->DC.NavLayersActiveMask = window->DC.NavLayersActiveMaskNext;
9470 window->DC.NavLayersActiveMaskNext = 0x00;
9471 window->DC.NavIsScrollPushableX = true;
9472 window->DC.NavHideHighlightOneFrame = false;
9473 window->DC.NavWindowHasScrollY = (window->ScrollMax.y > 0.0f);
9474
9475 window->DC.MenuBarAppending = false;
9476 window->DC.MenuColumns.Update(style.ItemSpacing.x, window_just_activated_by_user);
9477 window->DC.TreeDepth = 0;
9478 window->DC.TreeHasStackDataDepthMask = 0x00;
9479 window->DC.ChildWindows.resize(0);
9480 window->DC.StateStorage = &window->StateStorage;
9481 window->DC.CurrentColumns = NULL;
9482 window->DC.LayoutType = ImGuiLayoutType_Vertical;
9483 window->DC.ParentLayoutType = parent_window ? parent_window->DC.LayoutType : ImGuiLayoutType_Vertical;
9484
9485 window->DC.ItemWidth = window->ItemWidthDefault;
9486 window->DC.TextWrapPos = -1.0f; // disabled
9487 window->DC.ItemWidthStack.resize(0);
9488 window->DC.TextWrapPosStack.resize(0);
9489 if (flags & ImGuiWindowFlags_Modal)
9490 window->DC.ModalDimBgColor = ColorConvertFloat4ToU32(GetStyleColorVec4(ImGuiCol_ModalWindowDimBg));
9491
9492 if (window->AutoFitFramesX > 0)
9493 window->AutoFitFramesX--;
9494 if (window->AutoFitFramesY > 0)
9495 window->AutoFitFramesY--;
9496
9497 // Clear SetNextWindowXXX data (can aim to move this higher in the function)
9498 g.NextWindowData.ClearFlags();
9499
9500 // Apply focus (we need to call FocusWindow() AFTER setting DC.CursorStartPos so our initial navigation
9501 // reference rectangle can start around there) We ImGuiFocusRequestFlags_UnlessBelowModal to:
9502 // - Avoid focusing a window that is created outside of a modal. This will prevent active modal from being
9503 // closed.
9504 // - Position window behind the modal that is not a begin-parent of this window.
9505 if (want_focus)
9506 FocusWindow(window, ImGuiFocusRequestFlags_UnlessBelowModal);
9507 if (want_focus && window == g.NavWindow)
9508 NavInitWindow(
9509 window,
9510 false); // <-- this is in the way for us to be able to defer and sort reappearing FocusWindow() calls
9511
9512 // Close requested by platform window (apply to all windows in this viewport)
9513 if (p_open != NULL && window->Viewport->PlatformRequestClose && window->Viewport != GetMainViewport())
9514 {
9515 IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Window '%s' closed by PlatformRequestClose\n", window->Name);
9516 *p_open = false;
9517 g.NavWindowingToggleLayer =
9518 false; // Assume user mapped PlatformRequestClose on ALT-F4 so we disable ALT for menu toggle. False
9519 // positive not an issue. // FIXME-NAV: Try removing.
9520 }
9521
9522 // Pressing CTRL+C copy window content into the clipboard
9523 // [EXPERIMENTAL] Breaks on nested Begin/End pairs. We need to work that out and add better logging scope.
9524 // [EXPERIMENTAL] Text outputs has many issues.
9525 if (g.IO.ConfigWindowsCopyContentsWithCtrlC)
9526 if (g.NavWindow && g.NavWindow->RootWindow == window && g.ActiveId == 0 &&
9527 Shortcut(ImGuiMod_Ctrl | ImGuiKey_C))
9528 LogToClipboard(0);
9529
9530 // Title bar
9531 if (!(flags & ImGuiWindowFlags_NoTitleBar) && !window->DockIsActive)
9532 RenderWindowTitleBarContents(window,
9533 ImRect(title_bar_rect.Min.x + window->WindowBorderSize, title_bar_rect.Min.y,
9534 title_bar_rect.Max.x - window->WindowBorderSize, title_bar_rect.Max.y),
9535 name, p_open);
9536 else if (!(flags & ImGuiWindowFlags_NoTitleBar) && window->DockIsActive)
9537 LogText("%.*s\n", (int)(FindRenderedTextEnd(window->Name) - window->Name), window->Name);
9538
9539 // Clear hit test shape every frame
9540 window->HitTestHoleSize.x = window->HitTestHoleSize.y = 0;
9541
9542 if (flags & ImGuiWindowFlags_Tooltip)
9543 g.TooltipPreviousWindow = window;
9544
9545 if (g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable)
9546 {
9547 // Docking: Dragging a dockable window (or any of its child) turns it into a drag and drop source.
9548 // We need to do this _before_ we overwrite window->DC.LastItemId below because
9549 // BeginDockableDragDropSource() also overwrites it.
9550 if (g.MovingWindow == window && (window->RootWindowDockTree->Flags & ImGuiWindowFlags_NoDocking) == 0)
9551 BeginDockableDragDropSource(window);
9552
9553 // Docking: Any dockable window can act as a target. For dock node hosts we call
9554 // BeginDockableDragDropTarget() in DockNodeUpdate() instead.
9555 if (g.DragDropActive && !(flags & ImGuiWindowFlags_NoDocking))
9556 if (g.MovingWindow == NULL || g.MovingWindow->RootWindowDockTree != window)
9557 if ((window == window->RootWindowDockTree) && !(window->Flags & ImGuiWindowFlags_DockNodeHost))
9558 BeginDockableDragDropTarget(window);
9559 }
9560
9561 // We fill last item data based on Title Bar/Tab, in order for IsItemHovered() and IsItemActive() to be usable
9562 // after Begin(). This is useful to allow creating context menus on title bar only, etc.
9563 window->DC.WindowItemStatusFlags = ImGuiItemStatusFlags_None;
9564 window->DC.WindowItemStatusFlags |=
9565 IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0;
9566 SetLastItemDataForWindow(window, title_bar_rect);
9567
9568 // [DEBUG]
9569#ifndef IMGUI_DISABLE_DEBUG_TOOLS
9570 if (g.DebugLocateId != 0 && (window->ID == g.DebugLocateId || window->MoveId == g.DebugLocateId))
9571 DebugLocateItemResolveWithLastItem();
9572#endif
9573
9574 // [Test Engine] Register title bar / tab with MoveId.
9575#ifdef IMGUI_ENABLE_TEST_ENGINE
9576 if (!(window->Flags & ImGuiWindowFlags_NoTitleBar))
9577 {
9578 window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;
9579 IMGUI_TEST_ENGINE_ITEM_ADD(g.LastItemData.ID, g.LastItemData.Rect, &g.LastItemData);
9580 window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
9581 }
9582#endif
9583 }
9584 else
9585 {
9586 // Skip refresh always mark active
9587 if (window->SkipRefresh)
9588 SetWindowActiveForSkipRefresh(window);
9589
9590 // Append
9591 SetCurrentViewport(window, window->Viewport);
9592 SetCurrentWindow(window);
9593 g.NextWindowData.ClearFlags();
9594 SetLastItemDataForWindow(window, window->TitleBarRect());
9595 }
9596
9597 if (!(flags & ImGuiWindowFlags_DockNodeHost) && !window->SkipRefresh)
9598 PushClipRect(window->InnerClipRect.Min, window->InnerClipRect.Max, true);
9599
9600 // Clear 'accessed' flag last thing (After PushClipRect which will set the flag. We want the flag to stay false when
9601 // the default "Debug" window is unused)
9602 window->WriteAccessed = false;
9603 window->BeginCount++;
9604
9605 // Update visibility
9606 if (first_begin_of_the_frame && !window->SkipRefresh)
9607 {
9608 // When we are about to select this tab (which will only be visible on the _next frame_), flag it with a
9609 // non-zero HiddenFramesCannotSkipItems. This will have the important effect of actually returning true in
9610 // Begin() and not setting SkipItems, allowing an earlier submission of the window contents. This is analogous
9611 // to regular windows being hidden from one frame. It is especially important as e.g. nested TabBars would
9612 // otherwise generate flicker in the form of one empty frame, or focus requests won't be processed.
9613 if (window->DockIsActive && !window->DockTabIsVisible)
9614 {
9615 if (window->LastFrameJustFocused == g.FrameCount)
9616 window->HiddenFramesCannotSkipItems = 1;
9617 else
9618 window->HiddenFramesCanSkipItems = 1;
9619 }
9620
9621 if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_ChildMenu))
9622 {
9623 // Child window can be out of sight and have "negative" clip windows.
9624 // Mark them as collapsed so commands are skipped earlier (we can't manually collapse them because they have
9625 // no title bar).
9626 IM_ASSERT((flags & ImGuiWindowFlags_NoTitleBar) != 0 || window->DockIsActive);
9627 const bool nav_request =
9628 (window->ChildFlags & ImGuiChildFlags_NavFlattened) &&
9629 (g.NavAnyRequest && g.NavWindow && g.NavWindow->RootWindowForNav == window->RootWindowForNav);
9630 if (!g.LogEnabled && !nav_request)
9631 if (window->OuterRectClipped.Min.x >= window->OuterRectClipped.Max.x ||
9632 window->OuterRectClipped.Min.y >= window->OuterRectClipped.Max.y)
9633 {
9634 if (window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0)
9635 window->HiddenFramesCannotSkipItems = 1;
9636 else
9637 window->HiddenFramesCanSkipItems = 1;
9638 }
9639
9640 // Hide along with parent or if parent is collapsed
9641 if (parent_window && (parent_window->Collapsed || parent_window->HiddenFramesCanSkipItems > 0))
9642 window->HiddenFramesCanSkipItems = 1;
9643 if (parent_window && parent_window->HiddenFramesCannotSkipItems > 0)
9644 window->HiddenFramesCannotSkipItems = 1;
9645 }
9646
9647 // Don't render if style alpha is 0.0 at the time of Begin(). This is arbitrary and inconsistent but has been
9648 // there for a long while (may remove at some point)
9649 if (style.Alpha <= 0.0f)
9650 window->HiddenFramesCanSkipItems = 1;
9651
9652 // Update the Hidden flag
9653 bool hidden_regular = (window->HiddenFramesCanSkipItems > 0) || (window->HiddenFramesCannotSkipItems > 0);
9654 window->Hidden = hidden_regular || (window->HiddenFramesForRenderOnly > 0);
9655
9656 // Disable inputs for requested number of frames
9657 if (window->DisableInputsFrames > 0)
9658 {
9659 window->DisableInputsFrames--;
9660 window->Flags |= ImGuiWindowFlags_NoInputs;
9661 }
9662
9663 // Update the SkipItems flag, used to early out of all items functions (no layout required)
9664 bool skip_items = false;
9665 if (window->Collapsed || !window->Active || hidden_regular)
9666 if (window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0 && window->HiddenFramesCannotSkipItems <= 0)
9667 skip_items = true;
9668 window->SkipItems = skip_items;
9669
9670 // Restore NavLayersActiveMaskNext to previous value when not visible, so a CTRL+Tab back can use a safe value.
9671 if (window->SkipItems)
9672 window->DC.NavLayersActiveMaskNext = window->DC.NavLayersActiveMask;
9673
9674 // Sanity check: there are two spots which can set Appearing = true
9675 // - when 'window_just_activated_by_user' is set -> HiddenFramesCannotSkipItems is set -> SkipItems always false
9676 // - in BeginDocked() path when DockNodeIsVisible == DockTabIsVisible == true -> hidden _should_ be all zero //
9677 // FIXME: Not formally proven, hence the assert.
9678 if (window->SkipItems && !window->Appearing)
9679 IM_ASSERT(window->Appearing ==
9680 false); // Please report on GitHub if this triggers: https://github.com/ocornut/imgui/issues/4177
9681 }
9682 else if (first_begin_of_the_frame)
9683 {
9684 // Skip refresh mode
9685 window->SkipItems = true;
9686 }
9687
9688 // [DEBUG] io.ConfigDebugBeginReturnValue override return value to test Begin/End and BeginChild/EndChild behaviors.
9689 // (The implicit fallback window is NOT automatically ended allowing it to always be able to receive commands
9690 // without crashing)
9691#ifndef IMGUI_DISABLE_DEBUG_TOOLS
9692 if (!window->IsFallbackWindow)
9693 if ((g.IO.ConfigDebugBeginReturnValueOnce && window_just_created) ||
9694 (g.IO.ConfigDebugBeginReturnValueLoop && g.DebugBeginReturnValueCullDepth == g.CurrentWindowStack.Size))
9695 {
9696 if (window->AutoFitFramesX > 0)
9697 {
9698 window->AutoFitFramesX++;
9699 }
9700 if (window->AutoFitFramesY > 0)
9701 {
9702 window->AutoFitFramesY++;
9703 }
9704 return false;
9705 }
9706#endif
9707
9708 return !window->SkipItems;
9709}
9710
9711void ImGui::End()
9712{
9713 ImGuiContext &g = *GImGui;
9714 ImGuiWindow *window = g.CurrentWindow;
9715
9716 // Error checking: verify that user hasn't called End() too many times!
9717 if (g.CurrentWindowStack.Size <= 1 && g.WithinFrameScopeWithImplicitWindow)
9718 {
9719 IM_ASSERT_USER_ERROR(g.CurrentWindowStack.Size > 1, "Calling End() too many times!");
9720 return;
9721 }
9722 ImGuiWindowStackData &window_stack_data = g.CurrentWindowStack.back();
9723
9724 // Error checking: verify that user doesn't directly call End() on a child window.
9725 if ((window->Flags & ImGuiWindowFlags_ChildWindow) && !(window->Flags & ImGuiWindowFlags_DockNodeHost) &&
9726 !window->DockIsActive)
9727 IM_ASSERT_USER_ERROR(g.WithinEndChildID == window->ID, "Must call EndChild() and not End()!");
9728
9729 // Close anything that is open
9730 if (window->DC.CurrentColumns)
9731 EndColumns();
9732 if (!(window->Flags & ImGuiWindowFlags_DockNodeHost) && !window->SkipRefresh) // Pop inner window clip rectangle
9733 PopClipRect();
9734 PopFocusScope();
9735 if (window_stack_data.DisabledOverrideReenable && window->RootWindow == window)
9736 EndDisabledOverrideReenable();
9737
9738 if (window->SkipRefresh)
9739 {
9740 IM_ASSERT(window->DrawList == NULL);
9741 window->DrawList = &window->DrawListInst;
9742 }
9743
9744 // Stop logging
9745 if (g.LogWindow == window) // FIXME: add more options for scope of logging
9746 LogFinish();
9747
9748 if (window->DC.IsSetPos)
9749 ErrorCheckUsingSetCursorPosToExtendParentBoundaries();
9750
9751 // Docking: report contents sizes to parent to allow for auto-resize
9752 if (window->DockNode && window->DockTabIsVisible)
9753 if (ImGuiWindow *host_window = window->DockNode->HostWindow) // FIXME-DOCK
9754 host_window->DC.CursorMaxPos = window->DC.CursorMaxPos + window->WindowPadding - host_window->WindowPadding;
9755
9756 // Pop from window stack
9757 g.LastItemData = window_stack_data.ParentLastItemDataBackup;
9758 if (window->Flags & ImGuiWindowFlags_ChildMenu)
9759 g.BeginMenuDepth--;
9760 if (window->Flags & ImGuiWindowFlags_Popup)
9761 g.BeginPopupStack.pop_back();
9762
9763 // Error handling, state recovery
9764 if (g.IO.ConfigErrorRecovery)
9765 ErrorRecoveryTryToRecoverWindowState(&window_stack_data.StackSizesInBegin);
9766
9767 g.CurrentWindowStack.pop_back();
9768 SetCurrentWindow(g.CurrentWindowStack.Size == 0 ? NULL : g.CurrentWindowStack.back().Window);
9769 if (g.CurrentWindow)
9770 SetCurrentViewport(g.CurrentWindow, g.CurrentWindow->Viewport);
9771}
9772
9773// Important: this alone doesn't alter current ImDrawList state. This is called by PushFont/PopFont only.
9774void ImGui::SetCurrentFont(ImFont *font)
9775{
9776 ImGuiContext &g = *GImGui;
9777 IM_ASSERT(
9778 font &&
9779 font->IsLoaded()); // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ?
9780 IM_ASSERT(font->Scale > 0.0f);
9781 g.Font = font;
9782 g.FontBaseSize = ImMax(1.0f, g.IO.FontGlobalScale * g.Font->FontSize * g.Font->Scale);
9783 g.FontSize = g.CurrentWindow ? g.CurrentWindow->CalcFontSize() : 0.0f;
9784 g.FontScale = g.FontSize / g.Font->FontSize;
9785
9786 ImFontAtlas *atlas = g.Font->ContainerAtlas;
9787 g.DrawListSharedData.TexUvWhitePixel = atlas->TexUvWhitePixel;
9788 g.DrawListSharedData.TexUvLines = atlas->TexUvLines;
9789 g.DrawListSharedData.Font = g.Font;
9790 g.DrawListSharedData.FontSize = g.FontSize;
9791 g.DrawListSharedData.FontScale = g.FontScale;
9792}
9793
9794// Use ImDrawList::_SetTextureID(), making our shared g.FontStack[] authoritative against window-local ImDrawList.
9795// - Whereas ImDrawList::PushTextureID()/PopTextureID() is not to be used across Begin() calls.
9796// - Note that we don't propagate current texture id when e.g. Begin()-ing into a new window, we never really did...
9797// - Some code paths never really fully worked with multiple atlas textures.
9798// - The right-ish solution may be to remove _SetTextureID() and make AddText/RenderText lazily call
9799// PushTextureID()/PopTextureID()
9800// the same way AddImage() does, but then all other primitives would also need to? I don't think we should tackle
9801// this problem because we have a concrete need and a test bed for multiple atlas textures.
9802void ImGui::PushFont(ImFont *font)
9803{
9804 ImGuiContext &g = *GImGui;
9805 if (font == NULL)
9806 font = GetDefaultFont();
9807 g.FontStack.push_back(font);
9808 SetCurrentFont(font);
9809 g.CurrentWindow->DrawList->_SetTextureID(font->ContainerAtlas->TexID);
9810}
9811
9812void ImGui::PopFont()
9813{
9814 ImGuiContext &g = *GImGui;
9815 if (g.FontStack.Size <= 0)
9816 {
9817 IM_ASSERT_USER_ERROR(0, "Calling PopFont() too many times!");
9818 return;
9819 }
9820 g.FontStack.pop_back();
9821 ImFont *font = g.FontStack.Size == 0 ? GetDefaultFont() : g.FontStack.back();
9822 SetCurrentFont(font);
9823 g.CurrentWindow->DrawList->_SetTextureID(font->ContainerAtlas->TexID);
9824}
9825
9826void ImGui::PushItemFlag(ImGuiItemFlags option, bool enabled)
9827{
9828 ImGuiContext &g = *GImGui;
9829 ImGuiItemFlags item_flags = g.CurrentItemFlags;
9830 IM_ASSERT(item_flags == g.ItemFlagsStack.back());
9831 if (enabled)
9832 item_flags |= option;
9833 else
9834 item_flags &= ~option;
9835 g.CurrentItemFlags = item_flags;
9836 g.ItemFlagsStack.push_back(item_flags);
9837}
9838
9839void ImGui::PopItemFlag()
9840{
9841 ImGuiContext &g = *GImGui;
9842 if (g.ItemFlagsStack.Size <= 1)
9843 {
9844 IM_ASSERT_USER_ERROR(0, "Calling PopItemFlag() too many times!");
9845 return;
9846 }
9847 g.ItemFlagsStack.pop_back();
9848 g.CurrentItemFlags = g.ItemFlagsStack.back();
9849}
9850
9851// BeginDisabled()/EndDisabled()
9852// - Those can be nested but it cannot be used to enable an already disabled section (a single BeginDisabled(true) in
9853// the stack is enough to keep everything disabled)
9854// - Visually this is currently altering alpha, but it is expected that in a future styling system this would work
9855// differently.
9856// - Feedback welcome at https://github.com/ocornut/imgui/issues/211
9857// - BeginDisabled(false)/EndDisabled() essentially does nothing but is provided to facilitate use of boolean
9858// expressions.
9859// (as a micro-optimization: if you have tens of thousands of BeginDisabled(false)/EndDisabled() pairs, you might want
9860// to reformulate your code to avoid making those calls)
9861// - Note: mixing up BeginDisabled() and PushItemFlag(ImGuiItemFlags_Disabled) is currently NOT SUPPORTED.
9862void ImGui::BeginDisabled(bool disabled)
9863{
9864 ImGuiContext &g = *GImGui;
9865 bool was_disabled = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0;
9866 if (!was_disabled && disabled)
9867 {
9868 g.DisabledAlphaBackup = g.Style.Alpha;
9869 g.Style.Alpha *=
9870 g.Style.DisabledAlpha; // PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * g.Style.DisabledAlpha);
9871 }
9872 if (was_disabled || disabled)
9873 g.CurrentItemFlags |= ImGuiItemFlags_Disabled;
9874 g.ItemFlagsStack.push_back(g.CurrentItemFlags); // FIXME-OPT: can we simply skip this and use DisabledStackSize?
9875 g.DisabledStackSize++;
9876}
9877
9878void ImGui::EndDisabled()
9879{
9880 ImGuiContext &g = *GImGui;
9881 if (g.DisabledStackSize <= 0)
9882 {
9883 IM_ASSERT_USER_ERROR(0, "Calling EndDisabled() too many times!");
9884 return;
9885 }
9886 g.DisabledStackSize--;
9887 bool was_disabled = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0;
9888 // PopItemFlag();
9889 g.ItemFlagsStack.pop_back();
9890 g.CurrentItemFlags = g.ItemFlagsStack.back();
9891 if (was_disabled && (g.CurrentItemFlags & ImGuiItemFlags_Disabled) == 0)
9892 g.Style.Alpha = g.DisabledAlphaBackup; // PopStyleVar();
9893}
9894
9895// Could have been called BeginDisabledDisable() but it didn't want to be award nominated for most awkward function
9896// name. Ideally we would use a shared e.g. BeginDisabled()->BeginDisabledEx() but earlier needs to be optimal. The
9897// whole code for this is awkward, will reevaluate if we find a way to implement SetNextItemDisabled().
9898void ImGui::BeginDisabledOverrideReenable()
9899{
9900 ImGuiContext &g = *GImGui;
9901 IM_ASSERT(g.CurrentItemFlags & ImGuiItemFlags_Disabled);
9902 g.CurrentWindowStack.back().DisabledOverrideReenableAlphaBackup = g.Style.Alpha;
9903 g.Style.Alpha = g.DisabledAlphaBackup;
9904 g.CurrentItemFlags &= ~ImGuiItemFlags_Disabled;
9905 g.ItemFlagsStack.push_back(g.CurrentItemFlags);
9906 g.DisabledStackSize++;
9907}
9908
9909void ImGui::EndDisabledOverrideReenable()
9910{
9911 ImGuiContext &g = *GImGui;
9912 g.DisabledStackSize--;
9913 IM_ASSERT(g.DisabledStackSize > 0);
9914 g.ItemFlagsStack.pop_back();
9915 g.CurrentItemFlags = g.ItemFlagsStack.back();
9916 g.Style.Alpha = g.CurrentWindowStack.back().DisabledOverrideReenableAlphaBackup;
9917}
9918
9919void ImGui::PushTextWrapPos(float wrap_pos_x)
9920{
9921 ImGuiContext &g = *GImGui;
9922 ImGuiWindow *window = g.CurrentWindow;
9923 window->DC.TextWrapPosStack.push_back(window->DC.TextWrapPos);
9924 window->DC.TextWrapPos = wrap_pos_x;
9925}
9926
9927void ImGui::PopTextWrapPos()
9928{
9929 ImGuiContext &g = *GImGui;
9930 ImGuiWindow *window = g.CurrentWindow;
9931 if (window->DC.TextWrapPosStack.Size <= 0)
9932 {
9933 IM_ASSERT_USER_ERROR(0, "Calling PopTextWrapPos() too many times!");
9934 return;
9935 }
9936 window->DC.TextWrapPos = window->DC.TextWrapPosStack.back();
9937 window->DC.TextWrapPosStack.pop_back();
9938}
9939
9940static ImGuiWindow *GetCombinedRootWindow(ImGuiWindow *window, bool popup_hierarchy, bool dock_hierarchy)
9941{
9942 ImGuiWindow *last_window = NULL;
9943 while (last_window != window)
9944 {
9945 last_window = window;
9946 window = window->RootWindow;
9947 if (popup_hierarchy)
9948 window = window->RootWindowPopupTree;
9949 if (dock_hierarchy)
9950 window = window->RootWindowDockTree;
9951 }
9952 return window;
9953}
9954
9955bool ImGui::IsWindowChildOf(ImGuiWindow *window, ImGuiWindow *potential_parent, bool popup_hierarchy,
9956 bool dock_hierarchy)
9957{
9958 ImGuiWindow *window_root = GetCombinedRootWindow(window, popup_hierarchy, dock_hierarchy);
9959 if (window_root == potential_parent)
9960 return true;
9961 while (window != NULL)
9962 {
9963 if (window == potential_parent)
9964 return true;
9965 if (window == window_root) // end of chain
9966 return false;
9967 window = window->ParentWindow;
9968 }
9969 return false;
9970}
9971
9972bool ImGui::IsWindowWithinBeginStackOf(ImGuiWindow *window, ImGuiWindow *potential_parent)
9973{
9974 if (window->RootWindow == potential_parent)
9975 return true;
9976 while (window != NULL)
9977 {
9978 if (window == potential_parent)
9979 return true;
9980 window = window->ParentWindowInBeginStack;
9981 }
9982 return false;
9983}
9984
9985bool ImGui::IsWindowAbove(ImGuiWindow *potential_above, ImGuiWindow *potential_below)
9986{
9987 ImGuiContext &g = *GImGui;
9988
9989 // It would be saner to ensure that display layer is always reflected in the g.Windows[] order, which would likely
9990 // requires altering all manipulations of that array
9991 const int display_layer_delta = GetWindowDisplayLayer(potential_above) - GetWindowDisplayLayer(potential_below);
9992 if (display_layer_delta != 0)
9993 return display_layer_delta > 0;
9994
9995 for (int i = g.Windows.Size - 1; i >= 0; i--)
9996 {
9997 ImGuiWindow *candidate_window = g.Windows[i];
9998 if (candidate_window == potential_above)
9999 return true;
10000 if (candidate_window == potential_below)
10001 return false;
10002 }
10003 return false;
10004}
10005
10006// Is current window hovered and hoverable (e.g. not blocked by a popup/modal)? See ImGuiHoveredFlags_ for options.
10007// IMPORTANT: If you are trying to check whether your mouse should be dispatched to Dear ImGui or to your underlying
10008// app, you should not use this function! Use the 'io.WantCaptureMouse' boolean for that! Refer to FAQ entry "How can I
10009// tell whether to dispatch mouse/keyboard to Dear ImGui or my application?" for details.
10010bool ImGui::IsWindowHovered(ImGuiHoveredFlags flags)
10011{
10012 ImGuiContext &g = *GImGui;
10013 IM_ASSERT_USER_ERROR((flags & ~ImGuiHoveredFlags_AllowedMaskForIsWindowHovered) == 0,
10014 "Invalid flags for IsWindowHovered()!");
10015
10016 ImGuiWindow *ref_window = g.HoveredWindow;
10017 ImGuiWindow *cur_window = g.CurrentWindow;
10018 if (ref_window == NULL)
10019 return false;
10020
10021 if ((flags & ImGuiHoveredFlags_AnyWindow) == 0)
10022 {
10023 IM_ASSERT(cur_window); // Not inside a Begin()/End()
10024 const bool popup_hierarchy = (flags & ImGuiHoveredFlags_NoPopupHierarchy) == 0;
10025 const bool dock_hierarchy = (flags & ImGuiHoveredFlags_DockHierarchy) != 0;
10026 if (flags & ImGuiHoveredFlags_RootWindow)
10027 cur_window = GetCombinedRootWindow(cur_window, popup_hierarchy, dock_hierarchy);
10028
10029 bool result;
10030 if (flags & ImGuiHoveredFlags_ChildWindows)
10031 result = IsWindowChildOf(ref_window, cur_window, popup_hierarchy, dock_hierarchy);
10032 else
10033 result = (ref_window == cur_window);
10034 if (!result)
10035 return false;
10036 }
10037
10038 if (!IsWindowContentHoverable(ref_window, flags))
10039 return false;
10040 if (!(flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
10041 if (g.ActiveId != 0 && !g.ActiveIdAllowOverlap && g.ActiveId != ref_window->MoveId)
10042 return false;
10043
10044 // When changing hovered window we requires a bit of stationary delay before activating hover timer.
10045 // FIXME: We don't support delay other than stationary one for now, other delay would need a way
10046 // to fulfill the possibility that multiple IsWindowHovered() with varying flag could return true
10047 // for different windows of the hierarchy. Possibly need a Hash(Current+Flags) ==> (Timer) cache.
10048 // We can implement this for _Stationary because the data is linked to HoveredWindow rather than CurrentWindow.
10049 if (flags & ImGuiHoveredFlags_ForTooltip)
10050 flags = ApplyHoverFlagsForTooltip(flags, g.Style.HoverFlagsForTooltipMouse);
10051 if ((flags & ImGuiHoveredFlags_Stationary) != 0 && g.HoverWindowUnlockedStationaryId != ref_window->ID)
10052 return false;
10053
10054 return true;
10055}
10056
10057ImGuiID ImGui::GetWindowDockID()
10058{
10059 ImGuiContext &g = *GImGui;
10060 return g.CurrentWindow->DockId;
10061}
10062
10063bool ImGui::IsWindowDocked()
10064{
10065 ImGuiContext &g = *GImGui;
10066 return g.CurrentWindow->DockIsActive;
10067}
10068
10069float ImGui::GetWindowWidth()
10070{
10071 ImGuiWindow *window = GImGui->CurrentWindow;
10072 return window->Size.x;
10073}
10074
10075float ImGui::GetWindowHeight()
10076{
10077 ImGuiWindow *window = GImGui->CurrentWindow;
10078 return window->Size.y;
10079}
10080
10081ImVec2 ImGui::GetWindowPos()
10082{
10083 ImGuiContext &g = *GImGui;
10084 ImGuiWindow *window = g.CurrentWindow;
10085 return window->Pos;
10086}
10087
10088void ImGui::SetWindowPos(ImGuiWindow *window, const ImVec2 &pos, ImGuiCond cond)
10089{
10090 // Test condition (NB: bit 0 is always true) and clear flags for next time
10091 if (cond && (window->SetWindowPosAllowFlags & cond) == 0)
10092 return;
10093
10094 IM_ASSERT(cond == 0 ||
10095 ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
10096 window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
10097 window->SetWindowPosVal = ImVec2(FLT_MAX, FLT_MAX);
10098
10099 // Set
10100 const ImVec2 old_pos = window->Pos;
10101 window->Pos = ImTrunc(pos);
10102 ImVec2 offset = window->Pos - old_pos;
10103 if (offset.x == 0.0f && offset.y == 0.0f)
10104 return;
10105 MarkIniSettingsDirty(window);
10106 // FIXME: share code with TranslateWindow(), need to confirm whether the 3 rect modified by TranslateWindow() are
10107 // desirable here.
10108 window->DC.CursorPos += offset; // As we happen to move the window while it is being appended to (which is a bad
10109 // idea - will smear) let's at least offset the cursor
10110 window->DC.CursorMaxPos += offset; // And more importantly we need to offset CursorMaxPos/CursorStartPos this so
10111 // ContentSize calculation doesn't get affected.
10112 window->DC.IdealMaxPos += offset;
10113 window->DC.CursorStartPos += offset;
10114}
10115
10116void ImGui::SetWindowPos(const ImVec2 &pos, ImGuiCond cond)
10117{
10118 ImGuiWindow *window = GetCurrentWindowRead();
10119 SetWindowPos(window, pos, cond);
10120}
10121
10122void ImGui::SetWindowPos(const char *name, const ImVec2 &pos, ImGuiCond cond)
10123{
10124 if (ImGuiWindow *window = FindWindowByName(name))
10125 SetWindowPos(window, pos, cond);
10126}
10127
10128ImVec2 ImGui::GetWindowSize()
10129{
10130 ImGuiWindow *window = GetCurrentWindowRead();
10131 return window->Size;
10132}
10133
10134void ImGui::SetWindowSize(ImGuiWindow *window, const ImVec2 &size, ImGuiCond cond)
10135{
10136 // Test condition (NB: bit 0 is always true) and clear flags for next time
10137 if (cond && (window->SetWindowSizeAllowFlags & cond) == 0)
10138 return;
10139
10140 IM_ASSERT(cond == 0 ||
10141 ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
10142 window->SetWindowSizeAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
10143
10144 // Enable auto-fit (not done in BeginChild() path unless appearing or combined with
10145 // ImGuiChildFlags_AlwaysAutoResize)
10146 if ((window->Flags & ImGuiWindowFlags_ChildWindow) == 0 || window->Appearing ||
10147 (window->ChildFlags & ImGuiChildFlags_AlwaysAutoResize) != 0)
10148 window->AutoFitFramesX = (size.x <= 0.0f) ? 2 : 0;
10149 if ((window->Flags & ImGuiWindowFlags_ChildWindow) == 0 || window->Appearing ||
10150 (window->ChildFlags & ImGuiChildFlags_AlwaysAutoResize) != 0)
10151 window->AutoFitFramesY = (size.y <= 0.0f) ? 2 : 0;
10152
10153 // Set
10154 ImVec2 old_size = window->SizeFull;
10155 if (size.x <= 0.0f)
10156 window->AutoFitOnlyGrows = false;
10157 else
10158 window->SizeFull.x = IM_TRUNC(size.x);
10159 if (size.y <= 0.0f)
10160 window->AutoFitOnlyGrows = false;
10161 else
10162 window->SizeFull.y = IM_TRUNC(size.y);
10163 if (old_size.x != window->SizeFull.x || old_size.y != window->SizeFull.y)
10164 MarkIniSettingsDirty(window);
10165}
10166
10167void ImGui::SetWindowSize(const ImVec2 &size, ImGuiCond cond)
10168{
10169 SetWindowSize(GImGui->CurrentWindow, size, cond);
10170}
10171
10172void ImGui::SetWindowSize(const char *name, const ImVec2 &size, ImGuiCond cond)
10173{
10174 if (ImGuiWindow *window = FindWindowByName(name))
10175 SetWindowSize(window, size, cond);
10176}
10177
10178void ImGui::SetWindowCollapsed(ImGuiWindow *window, bool collapsed, ImGuiCond cond)
10179{
10180 // Test condition (NB: bit 0 is always true) and clear flags for next time
10181 if (cond && (window->SetWindowCollapsedAllowFlags & cond) == 0)
10182 return;
10183 window->SetWindowCollapsedAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
10184
10185 // Queue applying in Begin()
10186 if (window->WantCollapseToggle)
10187 window->Collapsed ^= 1;
10188 window->WantCollapseToggle = (window->Collapsed != collapsed);
10189}
10190
10191void ImGui::SetWindowHitTestHole(ImGuiWindow *window, const ImVec2 &pos, const ImVec2 &size)
10192{
10193 IM_ASSERT(window->HitTestHoleSize.x == 0); // We don't support multiple holes/hit test filters
10194 window->HitTestHoleSize = ImVec2ih(size);
10195 window->HitTestHoleOffset = ImVec2ih(pos - window->Pos);
10196}
10197
10198void ImGui::SetWindowHiddenAndSkipItemsForCurrentFrame(ImGuiWindow *window)
10199{
10200 window->Hidden = window->SkipItems = true;
10201 window->HiddenFramesCanSkipItems = 1;
10202}
10203
10204void ImGui::SetWindowCollapsed(bool collapsed, ImGuiCond cond)
10205{
10206 SetWindowCollapsed(GImGui->CurrentWindow, collapsed, cond);
10207}
10208
10209bool ImGui::IsWindowCollapsed()
10210{
10211 ImGuiWindow *window = GetCurrentWindowRead();
10212 return window->Collapsed;
10213}
10214
10215bool ImGui::IsWindowAppearing()
10216{
10217 ImGuiWindow *window = GetCurrentWindowRead();
10218 return window->Appearing;
10219}
10220
10221void ImGui::SetWindowCollapsed(const char *name, bool collapsed, ImGuiCond cond)
10222{
10223 if (ImGuiWindow *window = FindWindowByName(name))
10224 SetWindowCollapsed(window, collapsed, cond);
10225}
10226
10227void ImGui::SetNextWindowPos(const ImVec2 &pos, ImGuiCond cond, const ImVec2 &pivot)
10228{
10229 ImGuiContext &g = *GImGui;
10230 IM_ASSERT(cond == 0 ||
10231 ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
10232 g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasPos;
10233 g.NextWindowData.PosVal = pos;
10234 g.NextWindowData.PosPivotVal = pivot;
10235 g.NextWindowData.PosCond = cond ? cond : ImGuiCond_Always;
10236 g.NextWindowData.PosUndock = true;
10237}
10238
10239void ImGui::SetNextWindowSize(const ImVec2 &size, ImGuiCond cond)
10240{
10241 ImGuiContext &g = *GImGui;
10242 IM_ASSERT(cond == 0 ||
10243 ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
10244 g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasSize;
10245 g.NextWindowData.SizeVal = size;
10246 g.NextWindowData.SizeCond = cond ? cond : ImGuiCond_Always;
10247}
10248
10249// For each axis:
10250// - Use 0.0f as min or FLT_MAX as max if you don't want limits, e.g. size_min = (500.0f, 0.0f), size_max = (FLT_MAX,
10251// FLT_MAX) sets a minimum width.
10252// - Use -1 for both min and max of same axis to preserve current size which itself is a constraint.
10253// - See "Demo->Examples->Constrained-resizing window" for examples.
10254void ImGui::SetNextWindowSizeConstraints(const ImVec2 &size_min, const ImVec2 &size_max,
10255 ImGuiSizeCallback custom_callback, void *custom_callback_user_data)
10256{
10257 ImGuiContext &g = *GImGui;
10258 g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasSizeConstraint;
10259 g.NextWindowData.SizeConstraintRect = ImRect(size_min, size_max);
10260 g.NextWindowData.SizeCallback = custom_callback;
10261 g.NextWindowData.SizeCallbackUserData = custom_callback_user_data;
10262}
10263
10264// Content size = inner scrollable rectangle, padded with WindowPadding.
10265// SetNextWindowContentSize(ImVec2(100,100) + ImGuiWindowFlags_AlwaysAutoResize will always allow submitting a 100x100
10266// item.
10267void ImGui::SetNextWindowContentSize(const ImVec2 &size)
10268{
10269 ImGuiContext &g = *GImGui;
10270 g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasContentSize;
10271 g.NextWindowData.ContentSizeVal = ImTrunc(size);
10272}
10273
10274void ImGui::SetNextWindowScroll(const ImVec2 &scroll)
10275{
10276 ImGuiContext &g = *GImGui;
10277 g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasScroll;
10278 g.NextWindowData.ScrollVal = scroll;
10279}
10280
10281void ImGui::SetNextWindowCollapsed(bool collapsed, ImGuiCond cond)
10282{
10283 ImGuiContext &g = *GImGui;
10284 IM_ASSERT(cond == 0 ||
10285 ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
10286 g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasCollapsed;
10287 g.NextWindowData.CollapsedVal = collapsed;
10288 g.NextWindowData.CollapsedCond = cond ? cond : ImGuiCond_Always;
10289}
10290
10291void ImGui::SetNextWindowBgAlpha(float alpha)
10292{
10293 ImGuiContext &g = *GImGui;
10294 g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasBgAlpha;
10295 g.NextWindowData.BgAlphaVal = alpha;
10296}
10297
10298void ImGui::SetNextWindowViewport(ImGuiID id)
10299{
10300 ImGuiContext &g = *GImGui;
10301 g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasViewport;
10302 g.NextWindowData.ViewportId = id;
10303}
10304
10305void ImGui::SetNextWindowDockID(ImGuiID id, ImGuiCond cond)
10306{
10307 ImGuiContext &g = *GImGui;
10308 g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasDock;
10309 g.NextWindowData.DockCond = cond ? cond : ImGuiCond_Always;
10310 g.NextWindowData.DockId = id;
10311}
10312
10313void ImGui::SetNextWindowClass(const ImGuiWindowClass *window_class)
10314{
10315 ImGuiContext &g = *GImGui;
10316 IM_ASSERT((window_class->ViewportFlagsOverrideSet & window_class->ViewportFlagsOverrideClear) ==
10317 0); // Cannot set both set and clear for the same bit
10318 g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasWindowClass;
10319 g.NextWindowData.WindowClass = *window_class;
10320}
10321
10322// This is experimental and meant to be a toy for exploring a future/wider range of features.
10323void ImGui::SetNextWindowRefreshPolicy(ImGuiWindowRefreshFlags flags)
10324{
10325 ImGuiContext &g = *GImGui;
10326 g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasRefreshPolicy;
10327 g.NextWindowData.RefreshFlagsVal = flags;
10328}
10329
10330ImDrawList *ImGui::GetWindowDrawList()
10331{
10332 ImGuiWindow *window = GetCurrentWindow();
10333 return window->DrawList;
10334}
10335
10336float ImGui::GetWindowDpiScale()
10337{
10338 ImGuiContext &g = *GImGui;
10339 return g.CurrentDpiScale;
10340}
10341
10342ImGuiViewport *ImGui::GetWindowViewport()
10343{
10344 ImGuiContext &g = *GImGui;
10345 IM_ASSERT(g.CurrentViewport != NULL && g.CurrentViewport == g.CurrentWindow->Viewport);
10346 return g.CurrentViewport;
10347}
10348
10349ImFont *ImGui::GetFont()
10350{
10351 return GImGui->Font;
10352}
10353
10354float ImGui::GetFontSize()
10355{
10356 return GImGui->FontSize;
10357}
10358
10359ImVec2 ImGui::GetFontTexUvWhitePixel()
10360{
10361 return GImGui->DrawListSharedData.TexUvWhitePixel;
10362}
10363
10364void ImGui::SetWindowFontScale(float scale)
10365{
10366 IM_ASSERT(scale > 0.0f);
10367 ImGuiContext &g = *GImGui;
10368 ImGuiWindow *window = GetCurrentWindow();
10369 window->FontWindowScale = scale;
10370 g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize();
10371 g.FontScale = g.DrawListSharedData.FontScale = g.FontSize / g.Font->FontSize;
10372}
10373
10374void ImGui::PushFocusScope(ImGuiID id)
10375{
10376 ImGuiContext &g = *GImGui;
10378 data.ID = id;
10379 data.WindowID = g.CurrentWindow->ID;
10380 g.FocusScopeStack.push_back(data);
10381 g.CurrentFocusScopeId = id;
10382}
10383
10384void ImGui::PopFocusScope()
10385{
10386 ImGuiContext &g = *GImGui;
10387 if (g.FocusScopeStack.Size <= g.StackSizesInBeginForCurrentWindow->SizeOfFocusScopeStack)
10388 {
10389 IM_ASSERT_USER_ERROR(0, "Calling PopFocusScope() too many times!");
10390 return;
10391 }
10392 g.FocusScopeStack.pop_back();
10393 g.CurrentFocusScopeId = g.FocusScopeStack.Size ? g.FocusScopeStack.back().ID : 0;
10394}
10395
10396void ImGui::SetNavFocusScope(ImGuiID focus_scope_id)
10397{
10398 ImGuiContext &g = *GImGui;
10399 g.NavFocusScopeId = focus_scope_id;
10400 g.NavFocusRoute.resize(0); // Invalidate
10401 if (focus_scope_id == 0)
10402 return;
10403 IM_ASSERT(g.NavWindow != NULL);
10404
10405 // Store current path (in reverse order)
10406 if (focus_scope_id == g.CurrentFocusScopeId)
10407 {
10408 // Top of focus stack contains local focus scopes inside current window
10409 for (int n = g.FocusScopeStack.Size - 1; n >= 0 && g.FocusScopeStack.Data[n].WindowID == g.CurrentWindow->ID;
10410 n--)
10411 g.NavFocusRoute.push_back(g.FocusScopeStack.Data[n]);
10412 }
10413 else if (focus_scope_id == g.NavWindow->NavRootFocusScopeId)
10414 g.NavFocusRoute.push_back({focus_scope_id, g.NavWindow->ID});
10415 else
10416 return;
10417
10418 // Then follow on manually set ParentWindowForFocusRoute field (#6798)
10419 for (ImGuiWindow *window = g.NavWindow->ParentWindowForFocusRoute; window != NULL;
10420 window = window->ParentWindowForFocusRoute)
10421 g.NavFocusRoute.push_back({window->NavRootFocusScopeId, window->ID});
10422 IM_ASSERT(g.NavFocusRoute.Size < 100); // Maximum depth is technically 251 as per CalcRoutingScore(): 254 - 3
10423}
10424
10425// Focus = move navigation cursor, set scrolling, set focus window.
10426void ImGui::FocusItem()
10427{
10428 ImGuiContext &g = *GImGui;
10429 ImGuiWindow *window = g.CurrentWindow;
10430 IMGUI_DEBUG_LOG_FOCUS("FocusItem(0x%08x) in window \"%s\"\n", g.LastItemData.ID, window->Name);
10431 if (g.DragDropActive || g.MovingWindow != NULL) // FIXME: Opt-in flags for this?
10432 {
10433 IMGUI_DEBUG_LOG_FOCUS("FocusItem() ignored while DragDropActive!\n");
10434 return;
10435 }
10436
10437 ImGuiNavMoveFlags move_flags = ImGuiNavMoveFlags_IsTabbing | ImGuiNavMoveFlags_FocusApi |
10438 ImGuiNavMoveFlags_NoSetNavCursorVisible | ImGuiNavMoveFlags_NoSelect;
10439 ImGuiScrollFlags scroll_flags = window->Appearing
10440 ? ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_AlwaysCenterY
10441 : ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_KeepVisibleEdgeY;
10442 SetNavWindow(window);
10443 NavMoveRequestSubmit(ImGuiDir_None, ImGuiDir_Up, move_flags, scroll_flags);
10444 NavMoveRequestResolveWithLastItem(&g.NavMoveResultLocal);
10445}
10446
10447void ImGui::ActivateItemByID(ImGuiID id)
10448{
10449 ImGuiContext &g = *GImGui;
10450 g.NavNextActivateId = id;
10451 g.NavNextActivateFlags = ImGuiActivateFlags_None;
10452}
10453
10454// Note: this will likely be called ActivateItem() once we rework our Focus/Activation system!
10455// But ActivateItem() should function without altering scroll/focus?
10456void ImGui::SetKeyboardFocusHere(int offset)
10457{
10458 ImGuiContext &g = *GImGui;
10459 ImGuiWindow *window = g.CurrentWindow;
10460 IM_ASSERT(offset >= -1); // -1 is allowed but not below
10461 IMGUI_DEBUG_LOG_FOCUS("SetKeyboardFocusHere(%d) in window \"%s\"\n", offset, window->Name);
10462
10463 // It makes sense in the vast majority of cases to never interrupt a drag and drop.
10464 // When we refactor this function into ActivateItem() we may want to make this an option.
10465 // MovingWindow is protected from most user inputs using SetActiveIdUsingNavAndKeys(), but
10466 // is also automatically dropped in the event g.ActiveId is stolen.
10467 if (g.DragDropActive || g.MovingWindow != NULL)
10468 {
10469 IMGUI_DEBUG_LOG_FOCUS("SetKeyboardFocusHere() ignored while DragDropActive!\n");
10470 return;
10471 }
10472
10473 SetNavWindow(window);
10474
10475 ImGuiNavMoveFlags move_flags = ImGuiNavMoveFlags_IsTabbing | ImGuiNavMoveFlags_Activate |
10476 ImGuiNavMoveFlags_FocusApi | ImGuiNavMoveFlags_NoSetNavCursorVisible;
10477 ImGuiScrollFlags scroll_flags = window->Appearing
10478 ? ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_AlwaysCenterY
10479 : ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_KeepVisibleEdgeY;
10480 NavMoveRequestSubmit(
10481 ImGuiDir_None, offset < 0 ? ImGuiDir_Up : ImGuiDir_Down, move_flags,
10482 scroll_flags); // FIXME-NAV: Once we refactor tabbing, add LegacyApi flag to not activate non-inputable.
10483 if (offset == -1)
10484 {
10485 NavMoveRequestResolveWithLastItem(&g.NavMoveResultLocal);
10486 }
10487 else
10488 {
10489 g.NavTabbingDir = 1;
10490 g.NavTabbingCounter = offset + 1;
10491 }
10492}
10493
10494void ImGui::SetItemDefaultFocus()
10495{
10496 ImGuiContext &g = *GImGui;
10497 ImGuiWindow *window = g.CurrentWindow;
10498 if (!window->Appearing)
10499 return;
10500 if (g.NavWindow != window->RootWindowForNav || (!g.NavInitRequest && g.NavInitResult.ID == 0) ||
10501 g.NavLayer != window->DC.NavLayerCurrent)
10502 return;
10503
10504 g.NavInitRequest = false;
10505 NavApplyItemToResult(&g.NavInitResult);
10506 NavUpdateAnyRequestFlag();
10507
10508 // Scroll could be done in NavInitRequestApplyResult() via an opt-in flag (we however don't want regular init
10509 // requests to scroll)
10510 if (!window->ClipRect.Contains(g.LastItemData.Rect))
10511 ScrollToRectEx(window, g.LastItemData.Rect, ImGuiScrollFlags_None);
10512}
10513
10514void ImGui::SetStateStorage(ImGuiStorage *tree)
10515{
10516 ImGuiWindow *window = GImGui->CurrentWindow;
10517 window->DC.StateStorage = tree ? tree : &window->StateStorage;
10518}
10519
10520ImGuiStorage *ImGui::GetStateStorage()
10521{
10522 ImGuiWindow *window = GImGui->CurrentWindow;
10523 return window->DC.StateStorage;
10524}
10525
10526bool ImGui::IsRectVisible(const ImVec2 &size)
10527{
10528 ImGuiWindow *window = GImGui->CurrentWindow;
10529 return window->ClipRect.Overlaps(ImRect(window->DC.CursorPos, window->DC.CursorPos + size));
10530}
10531
10532bool ImGui::IsRectVisible(const ImVec2 &rect_min, const ImVec2 &rect_max)
10533{
10534 ImGuiWindow *window = GImGui->CurrentWindow;
10535 return window->ClipRect.Overlaps(ImRect(rect_min, rect_max));
10536}
10537
10538//-----------------------------------------------------------------------------
10539// [SECTION] ID STACK
10540//-----------------------------------------------------------------------------
10541
10542// This is one of the very rare legacy case where we use ImGuiWindow methods,
10543// it should ideally be flattened at some point but it's been used a lots by widgets.
10544IM_MSVC_RUNTIME_CHECKS_OFF
10545ImGuiID ImGuiWindow::GetID(const char *str, const char *str_end)
10546{
10547 ImGuiID seed = IDStack.back();
10548 ImGuiID id = ImHashStr(str, str_end ? (str_end - str) : 0, seed);
10549#ifndef IMGUI_DISABLE_DEBUG_TOOLS
10550 ImGuiContext &g = *Ctx;
10551 if (g.DebugHookIdInfo == id)
10552 ImGui::DebugHookIdInfo(id, ImGuiDataType_String, str, str_end);
10553#endif
10554 return id;
10555}
10556
10557ImGuiID ImGuiWindow::GetID(const void *ptr)
10558{
10559 ImGuiID seed = IDStack.back();
10560 ImGuiID id = ImHashData(&ptr, sizeof(void *), seed);
10561#ifndef IMGUI_DISABLE_DEBUG_TOOLS
10562 ImGuiContext &g = *Ctx;
10563 if (g.DebugHookIdInfo == id)
10564 ImGui::DebugHookIdInfo(id, ImGuiDataType_Pointer, ptr, NULL);
10565#endif
10566 return id;
10567}
10568
10569ImGuiID ImGuiWindow::GetID(int n)
10570{
10571 ImGuiID seed = IDStack.back();
10572 ImGuiID id = ImHashData(&n, sizeof(n), seed);
10573#ifndef IMGUI_DISABLE_DEBUG_TOOLS
10574 ImGuiContext &g = *Ctx;
10575 if (g.DebugHookIdInfo == id)
10576 ImGui::DebugHookIdInfo(id, ImGuiDataType_S32, (void *)(intptr_t)n, NULL);
10577#endif
10578 return id;
10579}
10580
10581// This is only used in rare/specific situations to manufacture an ID out of nowhere.
10582// FIXME: Consider instead storing last non-zero ID + count of successive zero-ID, and combine those?
10583ImGuiID ImGuiWindow::GetIDFromPos(const ImVec2 &p_abs)
10584{
10585 ImGuiID seed = IDStack.back();
10586 ImVec2 p_rel = ImGui::WindowPosAbsToRel(this, p_abs);
10587 ImGuiID id = ImHashData(&p_rel, sizeof(p_rel), seed);
10588 return id;
10589}
10590
10591// "
10592ImGuiID ImGuiWindow::GetIDFromRectangle(const ImRect &r_abs)
10593{
10594 ImGuiID seed = IDStack.back();
10595 ImRect r_rel = ImGui::WindowRectAbsToRel(this, r_abs);
10596 ImGuiID id = ImHashData(&r_rel, sizeof(r_rel), seed);
10597 return id;
10598}
10599
10600void ImGui::PushID(const char *str_id)
10601{
10602 ImGuiContext &g = *GImGui;
10603 ImGuiWindow *window = g.CurrentWindow;
10604 ImGuiID id = window->GetID(str_id);
10605 window->IDStack.push_back(id);
10606}
10607
10608void ImGui::PushID(const char *str_id_begin, const char *str_id_end)
10609{
10610 ImGuiContext &g = *GImGui;
10611 ImGuiWindow *window = g.CurrentWindow;
10612 ImGuiID id = window->GetID(str_id_begin, str_id_end);
10613 window->IDStack.push_back(id);
10614}
10615
10616void ImGui::PushID(const void *ptr_id)
10617{
10618 ImGuiContext &g = *GImGui;
10619 ImGuiWindow *window = g.CurrentWindow;
10620 ImGuiID id = window->GetID(ptr_id);
10621 window->IDStack.push_back(id);
10622}
10623
10624void ImGui::PushID(int int_id)
10625{
10626 ImGuiContext &g = *GImGui;
10627 ImGuiWindow *window = g.CurrentWindow;
10628 ImGuiID id = window->GetID(int_id);
10629 window->IDStack.push_back(id);
10630}
10631
10632// Push a given id value ignoring the ID stack as a seed.
10633void ImGui::PushOverrideID(ImGuiID id)
10634{
10635 ImGuiContext &g = *GImGui;
10636 ImGuiWindow *window = g.CurrentWindow;
10637#ifndef IMGUI_DISABLE_DEBUG_TOOLS
10638 if (g.DebugHookIdInfo == id)
10639 DebugHookIdInfo(id, ImGuiDataType_ID, NULL, NULL);
10640#endif
10641 window->IDStack.push_back(id);
10642}
10643
10644// Helper to avoid a common series of PushOverrideID -> GetID() -> PopID() call
10645// (note that when using this pattern, ID Stack Tool will tend to not display the intermediate stack level.
10646// for that to work we would need to do PushOverrideID() -> ItemAdd() -> PopID() which would alter widget code a little
10647// more)
10648ImGuiID ImGui::GetIDWithSeed(const char *str, const char *str_end, ImGuiID seed)
10649{
10650 ImGuiID id = ImHashStr(str, str_end ? (str_end - str) : 0, seed);
10651#ifndef IMGUI_DISABLE_DEBUG_TOOLS
10652 ImGuiContext &g = *GImGui;
10653 if (g.DebugHookIdInfo == id)
10654 DebugHookIdInfo(id, ImGuiDataType_String, str, str_end);
10655#endif
10656 return id;
10657}
10658
10659ImGuiID ImGui::GetIDWithSeed(int n, ImGuiID seed)
10660{
10661 ImGuiID id = ImHashData(&n, sizeof(n), seed);
10662#ifndef IMGUI_DISABLE_DEBUG_TOOLS
10663 ImGuiContext &g = *GImGui;
10664 if (g.DebugHookIdInfo == id)
10665 DebugHookIdInfo(id, ImGuiDataType_S32, (void *)(intptr_t)n, NULL);
10666#endif
10667 return id;
10668}
10669
10670void ImGui::PopID()
10671{
10672 ImGuiWindow *window = GImGui->CurrentWindow;
10673 if (window->IDStack.Size <= 1)
10674 {
10675 IM_ASSERT_USER_ERROR(0, "Calling PopID() too many times!");
10676 return;
10677 }
10678 window->IDStack.pop_back();
10679}
10680
10681ImGuiID ImGui::GetID(const char *str_id)
10682{
10683 ImGuiWindow *window = GImGui->CurrentWindow;
10684 return window->GetID(str_id);
10685}
10686
10687ImGuiID ImGui::GetID(const char *str_id_begin, const char *str_id_end)
10688{
10689 ImGuiWindow *window = GImGui->CurrentWindow;
10690 return window->GetID(str_id_begin, str_id_end);
10691}
10692
10693ImGuiID ImGui::GetID(const void *ptr_id)
10694{
10695 ImGuiWindow *window = GImGui->CurrentWindow;
10696 return window->GetID(ptr_id);
10697}
10698
10699ImGuiID ImGui::GetID(int int_id)
10700{
10701 ImGuiWindow *window = GImGui->CurrentWindow;
10702 return window->GetID(int_id);
10703}
10704IM_MSVC_RUNTIME_CHECKS_RESTORE
10705
10706//-----------------------------------------------------------------------------
10707// [SECTION] INPUTS
10708//-----------------------------------------------------------------------------
10709// - GetModForLRModKey() [Internal]
10710// - FixupKeyChord() [Internal]
10711// - GetKeyData() [Internal]
10712// - GetKeyIndex() [Internal]
10713// - GetKeyName()
10714// - GetKeyChordName() [Internal]
10715// - CalcTypematicRepeatAmount() [Internal]
10716// - GetTypematicRepeatRate() [Internal]
10717// - GetKeyPressedAmount() [Internal]
10718// - GetKeyMagnitude2d() [Internal]
10719//-----------------------------------------------------------------------------
10720// - UpdateKeyRoutingTable() [Internal]
10721// - GetRoutingIdFromOwnerId() [Internal]
10722// - GetShortcutRoutingData() [Internal]
10723// - CalcRoutingScore() [Internal]
10724// - SetShortcutRouting() [Internal]
10725// - TestShortcutRouting() [Internal]
10726//-----------------------------------------------------------------------------
10727// - IsKeyDown()
10728// - IsKeyPressed()
10729// - IsKeyReleased()
10730//-----------------------------------------------------------------------------
10731// - IsMouseDown()
10732// - IsMouseClicked()
10733// - IsMouseReleased()
10734// - IsMouseDoubleClicked()
10735// - GetMouseClickedCount()
10736// - IsMouseHoveringRect() [Internal]
10737// - IsMouseDragPastThreshold() [Internal]
10738// - IsMouseDragging()
10739// - GetMousePos()
10740// - SetMousePos() [Internal]
10741// - GetMousePosOnOpeningCurrentPopup()
10742// - IsMousePosValid()
10743// - IsAnyMouseDown()
10744// - GetMouseDragDelta()
10745// - ResetMouseDragDelta()
10746// - GetMouseCursor()
10747// - SetMouseCursor()
10748//-----------------------------------------------------------------------------
10749// - UpdateAliasKey()
10750// - GetMergedModsFromKeys()
10751// - UpdateKeyboardInputs()
10752// - UpdateMouseInputs()
10753//-----------------------------------------------------------------------------
10754// - LockWheelingWindow [Internal]
10755// - FindBestWheelingWindow [Internal]
10756// - UpdateMouseWheel() [Internal]
10757//-----------------------------------------------------------------------------
10758// - SetNextFrameWantCaptureKeyboard()
10759// - SetNextFrameWantCaptureMouse()
10760//-----------------------------------------------------------------------------
10761// - GetInputSourceName() [Internal]
10762// - DebugPrintInputEvent() [Internal]
10763// - UpdateInputEvents() [Internal]
10764//-----------------------------------------------------------------------------
10765// - GetKeyOwner() [Internal]
10766// - TestKeyOwner() [Internal]
10767// - SetKeyOwner() [Internal]
10768// - SetItemKeyOwner() [Internal]
10769// - Shortcut() [Internal]
10770//-----------------------------------------------------------------------------
10771
10772static ImGuiKeyChord GetModForLRModKey(ImGuiKey key)
10773{
10774 if (key == ImGuiKey_LeftCtrl || key == ImGuiKey_RightCtrl)
10775 return ImGuiMod_Ctrl;
10776 if (key == ImGuiKey_LeftShift || key == ImGuiKey_RightShift)
10777 return ImGuiMod_Shift;
10778 if (key == ImGuiKey_LeftAlt || key == ImGuiKey_RightAlt)
10779 return ImGuiMod_Alt;
10780 if (key == ImGuiKey_LeftSuper || key == ImGuiKey_RightSuper)
10781 return ImGuiMod_Super;
10782 return ImGuiMod_None;
10783}
10784
10785ImGuiKeyChord ImGui::FixupKeyChord(ImGuiKeyChord key_chord)
10786{
10787 // Add ImGuiMod_XXXX when a corresponding ImGuiKey_LeftXXX/ImGuiKey_RightXXX is specified.
10788 ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_);
10789 if (IsLRModKey(key))
10790 key_chord |= GetModForLRModKey(key);
10791 return key_chord;
10792}
10793
10794ImGuiKeyData *ImGui::GetKeyData(ImGuiContext *ctx, ImGuiKey key)
10795{
10796 ImGuiContext &g = *ctx;
10797
10798 // Special storage location for mods
10799 if (key & ImGuiMod_Mask_)
10800 key = ConvertSingleModFlagToKey(key);
10801
10802 IM_ASSERT(IsNamedKey(key) &&
10803 "Support for user key indices was dropped in favor of ImGuiKey. Please update backend & user code.");
10804 return &g.IO.KeysData[key - ImGuiKey_NamedKey_BEGIN];
10805}
10806
10807// Those names are provided for debugging purpose and are not meant to be saved persistently nor compared.
10808static const char *const GKeyNames[] = {
10809 "Tab",
10810 "LeftArrow",
10811 "RightArrow",
10812 "UpArrow",
10813 "DownArrow",
10814 "PageUp",
10815 "PageDown",
10816 "Home",
10817 "End",
10818 "Insert",
10819 "Delete",
10820 "Backspace",
10821 "Space",
10822 "Enter",
10823 "Escape",
10824 "LeftCtrl",
10825 "LeftShift",
10826 "LeftAlt",
10827 "LeftSuper",
10828 "RightCtrl",
10829 "RightShift",
10830 "RightAlt",
10831 "RightSuper",
10832 "Menu",
10833 "0",
10834 "1",
10835 "2",
10836 "3",
10837 "4",
10838 "5",
10839 "6",
10840 "7",
10841 "8",
10842 "9",
10843 "A",
10844 "B",
10845 "C",
10846 "D",
10847 "E",
10848 "F",
10849 "G",
10850 "H",
10851 "I",
10852 "J",
10853 "K",
10854 "L",
10855 "M",
10856 "N",
10857 "O",
10858 "P",
10859 "Q",
10860 "R",
10861 "S",
10862 "T",
10863 "U",
10864 "V",
10865 "W",
10866 "X",
10867 "Y",
10868 "Z",
10869 "F1",
10870 "F2",
10871 "F3",
10872 "F4",
10873 "F5",
10874 "F6",
10875 "F7",
10876 "F8",
10877 "F9",
10878 "F10",
10879 "F11",
10880 "F12",
10881 "F13",
10882 "F14",
10883 "F15",
10884 "F16",
10885 "F17",
10886 "F18",
10887 "F19",
10888 "F20",
10889 "F21",
10890 "F22",
10891 "F23",
10892 "F24",
10893 "Apostrophe",
10894 "Comma",
10895 "Minus",
10896 "Period",
10897 "Slash",
10898 "Semicolon",
10899 "Equal",
10900 "LeftBracket",
10901 "Backslash",
10902 "RightBracket",
10903 "GraveAccent",
10904 "CapsLock",
10905 "ScrollLock",
10906 "NumLock",
10907 "PrintScreen",
10908 "Pause",
10909 "Keypad0",
10910 "Keypad1",
10911 "Keypad2",
10912 "Keypad3",
10913 "Keypad4",
10914 "Keypad5",
10915 "Keypad6",
10916 "Keypad7",
10917 "Keypad8",
10918 "Keypad9",
10919 "KeypadDecimal",
10920 "KeypadDivide",
10921 "KeypadMultiply",
10922 "KeypadSubtract",
10923 "KeypadAdd",
10924 "KeypadEnter",
10925 "KeypadEqual",
10926 "AppBack",
10927 "AppForward",
10928 "Oem102",
10929 "GamepadStart",
10930 "GamepadBack",
10931 "GamepadFaceLeft",
10932 "GamepadFaceRight",
10933 "GamepadFaceUp",
10934 "GamepadFaceDown",
10935 "GamepadDpadLeft",
10936 "GamepadDpadRight",
10937 "GamepadDpadUp",
10938 "GamepadDpadDown",
10939 "GamepadL1",
10940 "GamepadR1",
10941 "GamepadL2",
10942 "GamepadR2",
10943 "GamepadL3",
10944 "GamepadR3",
10945 "GamepadLStickLeft",
10946 "GamepadLStickRight",
10947 "GamepadLStickUp",
10948 "GamepadLStickDown",
10949 "GamepadRStickLeft",
10950 "GamepadRStickRight",
10951 "GamepadRStickUp",
10952 "GamepadRStickDown",
10953 "MouseLeft",
10954 "MouseRight",
10955 "MouseMiddle",
10956 "MouseX1",
10957 "MouseX2",
10958 "MouseWheelX",
10959 "MouseWheelY",
10960 "ModCtrl",
10961 "ModShift",
10962 "ModAlt",
10963 "ModSuper", // ReservedForModXXX are showing the ModXXX names.
10964};
10965IM_STATIC_ASSERT(ImGuiKey_NamedKey_COUNT == IM_ARRAYSIZE(GKeyNames));
10966
10967const char *ImGui::GetKeyName(ImGuiKey key)
10968{
10969 if (key == ImGuiKey_None)
10970 return "None";
10971 IM_ASSERT(IsNamedKeyOrMod(key) &&
10972 "Support for user key indices was dropped in favor of ImGuiKey. Please update backend and user code.");
10973 if (key & ImGuiMod_Mask_)
10974 key = ConvertSingleModFlagToKey(key);
10975 if (!IsNamedKey(key))
10976 return "Unknown";
10977
10978 return GKeyNames[key - ImGuiKey_NamedKey_BEGIN];
10979}
10980
10981// Return untranslated names: on macOS, Cmd key will show as Ctrl, Ctrl key will show as super.
10982// Lifetime of return value: valid until next call to same function.
10983const char *ImGui::GetKeyChordName(ImGuiKeyChord key_chord)
10984{
10985 ImGuiContext &g = *GImGui;
10986
10987 const ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_);
10988 if (IsLRModKey(key))
10989 key_chord &= ~GetModForLRModKey(key); // Return "Ctrl+LeftShift" instead of "Ctrl+Shift+LeftShift"
10990 ImFormatString(g.TempKeychordName, IM_ARRAYSIZE(g.TempKeychordName), "%s%s%s%s%s",
10991 (key_chord & ImGuiMod_Ctrl) ? "Ctrl+" : "", (key_chord & ImGuiMod_Shift) ? "Shift+" : "",
10992 (key_chord & ImGuiMod_Alt) ? "Alt+" : "", (key_chord & ImGuiMod_Super) ? "Super+" : "",
10993 (key != ImGuiKey_None || key_chord == ImGuiKey_None) ? GetKeyName(key) : "");
10994 size_t len;
10995 if (key == ImGuiKey_None && key_chord != 0)
10996 if ((len = ImStrlen(g.TempKeychordName)) != 0) // Remove trailing '+'
10997 g.TempKeychordName[len - 1] = 0;
10998 return g.TempKeychordName;
10999}
11000
11001// t0 = previous time (e.g.: g.Time - g.IO.DeltaTime)
11002// t1 = current time (e.g.: g.Time)
11003// An event is triggered at:
11004// t = 0.0f t = repeat_delay, t = repeat_delay + repeat_rate*N
11005int ImGui::CalcTypematicRepeatAmount(float t0, float t1, float repeat_delay, float repeat_rate)
11006{
11007 if (t1 == 0.0f)
11008 return 1;
11009 if (t0 >= t1)
11010 return 0;
11011 if (repeat_rate <= 0.0f)
11012 return (t0 < repeat_delay) && (t1 >= repeat_delay);
11013 const int count_t0 = (t0 < repeat_delay) ? -1 : (int)((t0 - repeat_delay) / repeat_rate);
11014 const int count_t1 = (t1 < repeat_delay) ? -1 : (int)((t1 - repeat_delay) / repeat_rate);
11015 const int count = count_t1 - count_t0;
11016 return count;
11017}
11018
11019void ImGui::GetTypematicRepeatRate(ImGuiInputFlags flags, float *repeat_delay, float *repeat_rate)
11020{
11021 ImGuiContext &g = *GImGui;
11022 switch (flags & ImGuiInputFlags_RepeatRateMask_)
11023 {
11024 case ImGuiInputFlags_RepeatRateNavMove:
11025 *repeat_delay = g.IO.KeyRepeatDelay * 0.72f;
11026 *repeat_rate = g.IO.KeyRepeatRate * 0.80f;
11027 return;
11028 case ImGuiInputFlags_RepeatRateNavTweak:
11029 *repeat_delay = g.IO.KeyRepeatDelay * 0.72f;
11030 *repeat_rate = g.IO.KeyRepeatRate * 0.30f;
11031 return;
11032 case ImGuiInputFlags_RepeatRateDefault:
11033 default:
11034 *repeat_delay = g.IO.KeyRepeatDelay * 1.00f;
11035 *repeat_rate = g.IO.KeyRepeatRate * 1.00f;
11036 return;
11037 }
11038}
11039
11040// Return value representing the number of presses in the last time period, for the given repeat rate
11041// (most often returns 0 or 1. The result is generally only >1 when RepeatRate is smaller than DeltaTime, aka large
11042// DeltaTime or fast RepeatRate)
11043int ImGui::GetKeyPressedAmount(ImGuiKey key, float repeat_delay, float repeat_rate)
11044{
11045 ImGuiContext &g = *GImGui;
11046 const ImGuiKeyData *key_data = GetKeyData(key);
11047 if (!key_data->Down) // In theory this should already be encoded as (DownDuration < 0.0f), but testing this
11048 // facilitates eating mechanism (until we finish work on key ownership)
11049 return 0;
11050 const float t = key_data->DownDuration;
11051 return CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, repeat_delay, repeat_rate);
11052}
11053
11054// Return 2D vector representing the combination of four cardinal direction, with analog value support (for e.g.
11055// ImGuiKey_GamepadLStick* values).
11056ImVec2 ImGui::GetKeyMagnitude2d(ImGuiKey key_left, ImGuiKey key_right, ImGuiKey key_up, ImGuiKey key_down)
11057{
11058 return ImVec2(GetKeyData(key_right)->AnalogValue - GetKeyData(key_left)->AnalogValue,
11059 GetKeyData(key_down)->AnalogValue - GetKeyData(key_up)->AnalogValue);
11060}
11061
11062// Rewrite routing data buffers to strip old entries + sort by key to make queries not touch scattered data.
11063// Entries D,A,B,B,A,C,B --> A,A,B,B,B,C,D
11064// Index A:1 B:2 C:5 D:0 --> A:0 B:2 C:5 D:6
11065// See 'Metrics->Key Owners & Shortcut Routing' to visualize the result of that operation.
11066static void ImGui::UpdateKeyRoutingTable(ImGuiKeyRoutingTable *rt)
11067{
11068 ImGuiContext &g = *GImGui;
11069 rt->EntriesNext.resize(0);
11070 for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1))
11071 {
11072 const int new_routing_start_idx = rt->EntriesNext.Size;
11073 ImGuiKeyRoutingData *routing_entry;
11074 for (int old_routing_idx = rt->Index[key - ImGuiKey_NamedKey_BEGIN]; old_routing_idx != -1;
11075 old_routing_idx = routing_entry->NextEntryIndex)
11076 {
11077 routing_entry = &rt->Entries[old_routing_idx];
11078 routing_entry->RoutingCurrScore = routing_entry->RoutingNextScore;
11079 routing_entry->RoutingCurr = routing_entry->RoutingNext; // Update entry
11080 routing_entry->RoutingNext = ImGuiKeyOwner_NoOwner;
11081 routing_entry->RoutingNextScore = 255;
11082 if (routing_entry->RoutingCurr == ImGuiKeyOwner_NoOwner)
11083 continue;
11084 rt->EntriesNext.push_back(*routing_entry); // Write alive ones into new buffer
11085
11086 // Apply routing to owner if there's no owner already (RoutingCurr == None at this point)
11087 // This is the result of previous frame's SetShortcutRouting() call.
11088 if (routing_entry->Mods == g.IO.KeyMods)
11089 {
11090 ImGuiKeyOwnerData *owner_data = GetKeyOwnerData(&g, key);
11091 if (owner_data->OwnerCurr == ImGuiKeyOwner_NoOwner)
11092 {
11093 owner_data->OwnerCurr = routing_entry->RoutingCurr;
11094 // IMGUI_DEBUG_LOG("SetKeyOwner(%s, owner_id=0x%08X) via Routing\n", GetKeyName(key),
11095 // routing_entry->RoutingCurr);
11096 }
11097 }
11098 }
11099
11100 // Rewrite linked-list
11101 rt->Index[key - ImGuiKey_NamedKey_BEGIN] =
11102 (ImGuiKeyRoutingIndex)(new_routing_start_idx < rt->EntriesNext.Size ? new_routing_start_idx : -1);
11103 for (int n = new_routing_start_idx; n < rt->EntriesNext.Size; n++)
11104 rt->EntriesNext[n].NextEntryIndex = (ImGuiKeyRoutingIndex)((n + 1 < rt->EntriesNext.Size) ? n + 1 : -1);
11105 }
11106 rt->Entries.swap(rt->EntriesNext); // Swap new and old indexes
11107}
11108
11109// owner_id may be None/Any, but routing_id needs to be always be set, so we default to GetCurrentFocusScope().
11110static inline ImGuiID GetRoutingIdFromOwnerId(ImGuiID owner_id)
11111{
11112 ImGuiContext &g = *GImGui;
11113 return (owner_id != ImGuiKeyOwner_NoOwner && owner_id != ImGuiKeyOwner_Any) ? owner_id : g.CurrentFocusScopeId;
11114}
11115
11116ImGuiKeyRoutingData *ImGui::GetShortcutRoutingData(ImGuiKeyChord key_chord)
11117{
11118 // Majority of shortcuts will be Key + any number of Mods
11119 // We accept _Single_ mod with ImGuiKey_None.
11120 // - Shortcut(ImGuiKey_S | ImGuiMod_Ctrl); // Legal
11121 // - Shortcut(ImGuiKey_S | ImGuiMod_Ctrl | ImGuiMod_Shift); // Legal
11122 // - Shortcut(ImGuiMod_Ctrl); // Legal
11123 // - Shortcut(ImGuiMod_Ctrl | ImGuiMod_Shift); // Not legal
11124 ImGuiContext &g = *GImGui;
11125 ImGuiKeyRoutingTable *rt = &g.KeysRoutingTable;
11126 ImGuiKeyRoutingData *routing_data;
11127 ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_);
11128 ImGuiKey mods = (ImGuiKey)(key_chord & ImGuiMod_Mask_);
11129 if (key == ImGuiKey_None)
11130 key = ConvertSingleModFlagToKey(mods);
11131 IM_ASSERT(IsNamedKey(key));
11132
11133 // Get (in the majority of case, the linked list will have one element so this should be 2 reads.
11134 // Subsequent elements will be contiguous in memory as list is sorted/rebuilt in NewFrame).
11135 for (ImGuiKeyRoutingIndex idx = rt->Index[key - ImGuiKey_NamedKey_BEGIN]; idx != -1;
11136 idx = routing_data->NextEntryIndex)
11137 {
11138 routing_data = &rt->Entries[idx];
11139 if (routing_data->Mods == mods)
11140 return routing_data;
11141 }
11142
11143 // Add to linked-list
11144 ImGuiKeyRoutingIndex routing_data_idx = (ImGuiKeyRoutingIndex)rt->Entries.Size;
11145 rt->Entries.push_back(ImGuiKeyRoutingData());
11146 routing_data = &rt->Entries[routing_data_idx];
11147 routing_data->Mods = (ImU16)mods;
11148 routing_data->NextEntryIndex = rt->Index[key - ImGuiKey_NamedKey_BEGIN]; // Setup linked list
11149 rt->Index[key - ImGuiKey_NamedKey_BEGIN] = routing_data_idx;
11150 return routing_data;
11151}
11152
11153// Current score encoding (lower is highest priority):
11154// - 0: ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteOverActive
11155// - 1: ImGuiInputFlags_ActiveItem or ImGuiInputFlags_RouteFocused (if item active)
11156// - 2: ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteOverFocused
11157// - 3+: ImGuiInputFlags_RouteFocused (if window in focus-stack)
11158// - 254: ImGuiInputFlags_RouteGlobal
11159// - 255: never route
11160// 'flags' should include an explicit routing policy
11161static int CalcRoutingScore(ImGuiID focus_scope_id, ImGuiID owner_id, ImGuiInputFlags flags)
11162{
11163 ImGuiContext &g = *GImGui;
11164 if (flags & ImGuiInputFlags_RouteFocused)
11165 {
11166 // ActiveID gets top priority
11167 // (we don't check g.ActiveIdUsingAllKeys here. Routing is applied but if input ownership is tested later it may
11168 // discard it)
11169 if (owner_id != 0 && g.ActiveId == owner_id)
11170 return 1;
11171
11172 // Score based on distance to focused window (lower is better)
11173 // Assuming both windows are submitting a routing request,
11174 // - When Window....... is focused -> Window scores 3 (best), Window/ChildB scores 255 (no match)
11175 // - When Window/ChildB is focused -> Window scores 4, Window/ChildB scores 3 (best)
11176 // Assuming only WindowA is submitting a routing request,
11177 // - When Window/ChildB is focused -> Window scores 4 (best), Window/ChildB doesn't have a score.
11178 // This essentially follow the window->ParentWindowForFocusRoute chain.
11179 if (focus_scope_id == 0)
11180 return 255;
11181 for (int index_in_focus_path = 0; index_in_focus_path < g.NavFocusRoute.Size; index_in_focus_path++)
11182 if (g.NavFocusRoute.Data[index_in_focus_path].ID == focus_scope_id)
11183 return 3 + index_in_focus_path;
11184 return 255;
11185 }
11186 else if (flags & ImGuiInputFlags_RouteActive)
11187 {
11188 if (owner_id != 0 && g.ActiveId == owner_id)
11189 return 1;
11190 return 255;
11191 }
11192 else if (flags & ImGuiInputFlags_RouteGlobal)
11193 {
11194 if (flags & ImGuiInputFlags_RouteOverActive)
11195 return 0;
11196 if (flags & ImGuiInputFlags_RouteOverFocused)
11197 return 2;
11198 return 254;
11199 }
11200 IM_ASSERT(0);
11201 return 0;
11202}
11203
11204// - We need this to filter some Shortcut() routes when an item e.g. an InputText() is active
11205// e.g. ImGuiKey_G won't be considered a shortcut when item is active, but ImGuiMod|ImGuiKey_G can be.
11206// - This is also used by UpdateInputEvents() to avoid trickling in the most common case of e.g. pressing ImGuiKey_G
11207// also emitting a G character.
11208static bool IsKeyChordPotentiallyCharInput(ImGuiKeyChord key_chord)
11209{
11210 // Mimic 'ignore_char_inputs' logic in InputText()
11211 ImGuiContext &g = *GImGui;
11212
11213 // When the right mods are pressed it cannot be a char input so we won't filter the shortcut out.
11214 ImGuiKey mods = (ImGuiKey)(key_chord & ImGuiMod_Mask_);
11215 const bool ignore_char_inputs =
11216 ((mods & ImGuiMod_Ctrl) && !(mods & ImGuiMod_Alt)) || (g.IO.ConfigMacOSXBehaviors && (mods & ImGuiMod_Ctrl));
11217 if (ignore_char_inputs)
11218 return false;
11219
11220 // Return true for A-Z, 0-9 and other keys associated to char inputs. Other keys such as F1-F12 won't be filtered.
11221 ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_);
11222 if (key == ImGuiKey_None)
11223 return false;
11224 return g.KeysMayBeCharInput.TestBit(key);
11225}
11226
11227// Request a desired route for an input chord (key + mods).
11228// Return true if the route is available this frame.
11229// - Routes and key ownership are attributed at the beginning of next frame based on best score and mod state.
11230// (Conceptually this does a "Submit for next frame" + "Test for current frame".
11231// As such, it could be called TrySetXXX or SubmitXXX, or the Submit and Test operations should be separate.)
11232bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiInputFlags flags, ImGuiID owner_id)
11233{
11234 ImGuiContext &g = *GImGui;
11235 if ((flags & ImGuiInputFlags_RouteTypeMask_) == 0)
11236 flags |= ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteOverFocused |
11237 ImGuiInputFlags_RouteOverActive; // IMPORTANT: This is the default for SetShortcutRouting() but NOT
11238 // Shortcut()
11239 else
11240 IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiInputFlags_RouteTypeMask_)); // Check that only 1 routing flag is used
11241 IM_ASSERT(owner_id != ImGuiKeyOwner_Any && owner_id != ImGuiKeyOwner_NoOwner);
11242 if (flags &
11243 (ImGuiInputFlags_RouteOverFocused | ImGuiInputFlags_RouteOverActive | ImGuiInputFlags_RouteUnlessBgFocused))
11244 IM_ASSERT(flags & ImGuiInputFlags_RouteGlobal);
11245
11246 // Add ImGuiMod_XXXX when a corresponding ImGuiKey_LeftXXX/ImGuiKey_RightXXX is specified.
11247 key_chord = FixupKeyChord(key_chord);
11248
11249 // [DEBUG] Debug break requested by user
11250 if (g.DebugBreakInShortcutRouting == key_chord)
11251 IM_DEBUG_BREAK();
11252
11253 if (flags & ImGuiInputFlags_RouteUnlessBgFocused)
11254 if (g.NavWindow == NULL)
11255 return false;
11256
11257 // Note how ImGuiInputFlags_RouteAlways won't set routing and thus won't set owner. May want to rework this?
11258 if (flags & ImGuiInputFlags_RouteAlways)
11259 {
11260 IMGUI_DEBUG_LOG_INPUTROUTING("SetShortcutRouting(%s, flags=%04X, owner_id=0x%08X) -> always, no register\n",
11261 GetKeyChordName(key_chord), flags, owner_id);
11262 return true;
11263 }
11264
11265 // Specific culling when there's an active item.
11266 if (g.ActiveId != 0 && g.ActiveId != owner_id)
11267 {
11268 if (flags & ImGuiInputFlags_RouteActive)
11269 return false;
11270
11271 // Cull shortcuts with no modifiers when it could generate a character.
11272 // e.g. Shortcut(ImGuiKey_G) also generates 'g' character, should not trigger when InputText() is active.
11273 // but Shortcut(Ctrl+G) should generally trigger when InputText() is active.
11274 // TL;DR: lettered shortcut with no mods or with only Alt mod will not trigger while an item reading text input
11275 // is active. (We cannot filter based on io.InputQueueCharacters[] contents because of trickling and key<>chars
11276 // submission order are undefined)
11277 if (g.IO.WantTextInput && IsKeyChordPotentiallyCharInput(key_chord))
11278 {
11279 IMGUI_DEBUG_LOG_INPUTROUTING(
11280 "SetShortcutRouting(%s, flags=%04X, owner_id=0x%08X) -> filtered as potential char input\n",
11281 GetKeyChordName(key_chord), flags, owner_id);
11282 return false;
11283 }
11284
11285 // ActiveIdUsingAllKeyboardKeys trumps all for ActiveId
11286 if ((flags & ImGuiInputFlags_RouteOverActive) == 0 && g.ActiveIdUsingAllKeyboardKeys)
11287 {
11288 ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_);
11289 if (key == ImGuiKey_None)
11290 key = ConvertSingleModFlagToKey((ImGuiKey)(key_chord & ImGuiMod_Mask_));
11291 if (key >= ImGuiKey_Keyboard_BEGIN && key < ImGuiKey_Keyboard_END)
11292 return false;
11293 }
11294 }
11295
11296 // Where do we evaluate route for?
11297 ImGuiID focus_scope_id = g.CurrentFocusScopeId;
11298 if (flags & ImGuiInputFlags_RouteFromRootWindow)
11299 focus_scope_id = g.CurrentWindow->RootWindow->ID; // See PushFocusScope() call in Begin()
11300
11301 const int score = CalcRoutingScore(focus_scope_id, owner_id, flags);
11302 IMGUI_DEBUG_LOG_INPUTROUTING("SetShortcutRouting(%s, flags=%04X, owner_id=0x%08X) -> score %d\n",
11303 GetKeyChordName(key_chord), flags, owner_id, score);
11304 if (score == 255)
11305 return false;
11306
11307 // Submit routing for NEXT frame (assuming score is sufficient)
11308 // FIXME: Could expose a way to use a "serve last" policy for same score resolution (using <= instead of <).
11309 ImGuiKeyRoutingData *routing_data = GetShortcutRoutingData(key_chord);
11310 // const bool set_route = (flags & ImGuiInputFlags_ServeLast) ? (score <= routing_data->RoutingNextScore) : (score <
11311 // routing_data->RoutingNextScore);
11312 if (score < routing_data->RoutingNextScore)
11313 {
11314 routing_data->RoutingNext = owner_id;
11315 routing_data->RoutingNextScore = (ImU8)score;
11316 }
11317
11318 // Return routing state for CURRENT frame
11319 if (routing_data->RoutingCurr == owner_id)
11320 IMGUI_DEBUG_LOG_INPUTROUTING("--> granting current route\n");
11321 return routing_data->RoutingCurr == owner_id;
11322}
11323
11324// Currently unused by core (but used by tests)
11325// Note: this cannot be turned into GetShortcutRouting() because we do the owner_id->routing_id translation, name would
11326// be more misleading.
11327bool ImGui::TestShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id)
11328{
11329 const ImGuiID routing_id = GetRoutingIdFromOwnerId(owner_id);
11330 key_chord = FixupKeyChord(key_chord);
11331 ImGuiKeyRoutingData *routing_data = GetShortcutRoutingData(key_chord); // FIXME: Could avoid creating entry.
11332 return routing_data->RoutingCurr == routing_id;
11333}
11334
11335// Note that Dear ImGui doesn't know the meaning/semantic of ImGuiKey from 0..511: they are legacy native keycodes.
11336// Consider transitioning from 'IsKeyDown(MY_ENGINE_KEY_A)' (<1.87) to IsKeyDown(ImGuiKey_A) (>= 1.87)
11337bool ImGui::IsKeyDown(ImGuiKey key)
11338{
11339 return IsKeyDown(key, ImGuiKeyOwner_Any);
11340}
11341
11342bool ImGui::IsKeyDown(ImGuiKey key, ImGuiID owner_id)
11343{
11344 const ImGuiKeyData *key_data = GetKeyData(key);
11345 if (!key_data->Down)
11346 return false;
11347 if (!TestKeyOwner(key, owner_id))
11348 return false;
11349 return true;
11350}
11351
11352bool ImGui::IsKeyPressed(ImGuiKey key, bool repeat)
11353{
11354 return IsKeyPressed(key, repeat ? ImGuiInputFlags_Repeat : ImGuiInputFlags_None, ImGuiKeyOwner_Any);
11355}
11356
11357// Important: unlike legacy IsKeyPressed(ImGuiKey, bool repeat=true) which DEFAULT to repeat, this requires EXPLICIT
11358// repeat.
11359bool ImGui::IsKeyPressed(ImGuiKey key, ImGuiInputFlags flags, ImGuiID owner_id)
11360{
11361 const ImGuiKeyData *key_data = GetKeyData(key);
11362 if (!key_data->Down) // In theory this should already be encoded as (DownDuration < 0.0f), but testing this
11363 // facilitates eating mechanism (until we finish work on key ownership)
11364 return false;
11365 const float t = key_data->DownDuration;
11366 if (t < 0.0f)
11367 return false;
11368 IM_ASSERT((flags & ~ImGuiInputFlags_SupportedByIsKeyPressed) == 0); // Passing flags not supported by this function!
11369 if (flags & (ImGuiInputFlags_RepeatRateMask_ |
11370 ImGuiInputFlags_RepeatUntilMask_)) // Setting any _RepeatXXX option enables _Repeat
11371 flags |= ImGuiInputFlags_Repeat;
11372
11373 bool pressed = (t == 0.0f);
11374 if (!pressed && (flags & ImGuiInputFlags_Repeat) != 0)
11375 {
11376 float repeat_delay, repeat_rate;
11377 GetTypematicRepeatRate(flags, &repeat_delay, &repeat_rate);
11378 pressed = (t > repeat_delay) && GetKeyPressedAmount(key, repeat_delay, repeat_rate) > 0;
11379 if (pressed && (flags & ImGuiInputFlags_RepeatUntilMask_))
11380 {
11381 // Slightly bias 'key_pressed_time' as DownDuration is an accumulation of DeltaTime which we compare to an
11382 // absolute time value. Ideally we'd replace DownDuration with KeyPressedTime but it would break user's
11383 // code.
11384 ImGuiContext &g = *GImGui;
11385 double key_pressed_time = g.Time - t + 0.00001f;
11386 if ((flags & ImGuiInputFlags_RepeatUntilKeyModsChange) && (g.LastKeyModsChangeTime > key_pressed_time))
11387 pressed = false;
11388 if ((flags & ImGuiInputFlags_RepeatUntilKeyModsChangeFromNone) &&
11389 (g.LastKeyModsChangeFromNoneTime > key_pressed_time))
11390 pressed = false;
11391 if ((flags & ImGuiInputFlags_RepeatUntilOtherKeyPress) && (g.LastKeyboardKeyPressTime > key_pressed_time))
11392 pressed = false;
11393 }
11394 }
11395 if (!pressed)
11396 return false;
11397 if (!TestKeyOwner(key, owner_id))
11398 return false;
11399 return true;
11400}
11401
11402bool ImGui::IsKeyReleased(ImGuiKey key)
11403{
11404 return IsKeyReleased(key, ImGuiKeyOwner_Any);
11405}
11406
11407bool ImGui::IsKeyReleased(ImGuiKey key, ImGuiID owner_id)
11408{
11409 const ImGuiKeyData *key_data = GetKeyData(key);
11410 if (key_data->DownDurationPrev < 0.0f || key_data->Down)
11411 return false;
11412 if (!TestKeyOwner(key, owner_id))
11413 return false;
11414 return true;
11415}
11416
11417bool ImGui::IsMouseDown(ImGuiMouseButton button)
11418{
11419 ImGuiContext &g = *GImGui;
11420 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
11421 return g.IO.MouseDown[button] &&
11422 TestKeyOwner(MouseButtonToKey(button),
11423 ImGuiKeyOwner_Any); // should be same as IsKeyDown(MouseButtonToKey(button), ImGuiKeyOwner_Any),
11424 // but this allows legacy code hijacking the io.Mousedown[] array.
11425}
11426
11427bool ImGui::IsMouseDown(ImGuiMouseButton button, ImGuiID owner_id)
11428{
11429 ImGuiContext &g = *GImGui;
11430 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
11431 return g.IO.MouseDown[button] &&
11432 TestKeyOwner(MouseButtonToKey(button),
11433 owner_id); // Should be same as IsKeyDown(MouseButtonToKey(button), owner_id), but this allows
11434 // legacy code hijacking the io.Mousedown[] array.
11435}
11436
11437bool ImGui::IsMouseClicked(ImGuiMouseButton button, bool repeat)
11438{
11439 return IsMouseClicked(button, repeat ? ImGuiInputFlags_Repeat : ImGuiInputFlags_None, ImGuiKeyOwner_Any);
11440}
11441
11442bool ImGui::IsMouseClicked(ImGuiMouseButton button, ImGuiInputFlags flags, ImGuiID owner_id)
11443{
11444 ImGuiContext &g = *GImGui;
11445 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
11446 if (!g.IO.MouseDown[button]) // In theory this should already be encoded as (DownDuration < 0.0f), but testing this
11447 // facilitates eating mechanism (until we finish work on key ownership)
11448 return false;
11449 const float t = g.IO.MouseDownDuration[button];
11450 if (t < 0.0f)
11451 return false;
11452 IM_ASSERT((flags & ~ImGuiInputFlags_SupportedByIsMouseClicked) ==
11453 0); // Passing flags not supported by this function! // FIXME: Could support RepeatRate and RepeatUntil
11454 // flags here.
11455
11456 const bool repeat = (flags & ImGuiInputFlags_Repeat) != 0;
11457 const bool pressed =
11458 (t == 0.0f) || (repeat && t > g.IO.KeyRepeatDelay &&
11459 CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0);
11460 if (!pressed)
11461 return false;
11462
11463 if (!TestKeyOwner(MouseButtonToKey(button), owner_id))
11464 return false;
11465
11466 return true;
11467}
11468
11469bool ImGui::IsMouseReleased(ImGuiMouseButton button)
11470{
11471 ImGuiContext &g = *GImGui;
11472 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
11473 return g.IO.MouseReleased[button] &&
11474 TestKeyOwner(
11475 MouseButtonToKey(button),
11476 ImGuiKeyOwner_Any); // Should be same as IsKeyReleased(MouseButtonToKey(button), ImGuiKeyOwner_Any)
11477}
11478
11479bool ImGui::IsMouseReleased(ImGuiMouseButton button, ImGuiID owner_id)
11480{
11481 ImGuiContext &g = *GImGui;
11482 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
11483 return g.IO.MouseReleased[button] &&
11484 TestKeyOwner(MouseButtonToKey(button),
11485 owner_id); // Should be same as IsKeyReleased(MouseButtonToKey(button), owner_id)
11486}
11487
11488// Use if you absolutely need to distinguish single-click from double-click by introducing a delay.
11489// Generally use with 'delay >= io.MouseDoubleClickTime' + combined with a 'io.MouseClickedLastCount == 1' test.
11490// This is a very rarely used UI idiom, but some apps use this: e.g. MS Explorer single click on an icon to rename.
11491bool ImGui::IsMouseReleasedWithDelay(ImGuiMouseButton button, float delay)
11492{
11493 ImGuiContext &g = *GImGui;
11494 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
11495 const float time_since_release = (float)(g.Time - g.IO.MouseReleasedTime[button]);
11496 return !IsMouseDown(button) && (time_since_release - g.IO.DeltaTime < delay) && (time_since_release >= delay);
11497}
11498
11499bool ImGui::IsMouseDoubleClicked(ImGuiMouseButton button)
11500{
11501 ImGuiContext &g = *GImGui;
11502 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
11503 return g.IO.MouseClickedCount[button] == 2 && TestKeyOwner(MouseButtonToKey(button), ImGuiKeyOwner_Any);
11504}
11505
11506bool ImGui::IsMouseDoubleClicked(ImGuiMouseButton button, ImGuiID owner_id)
11507{
11508 ImGuiContext &g = *GImGui;
11509 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
11510 return g.IO.MouseClickedCount[button] == 2 && TestKeyOwner(MouseButtonToKey(button), owner_id);
11511}
11512
11513int ImGui::GetMouseClickedCount(ImGuiMouseButton button)
11514{
11515 ImGuiContext &g = *GImGui;
11516 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
11517 return g.IO.MouseClickedCount[button];
11518}
11519
11520// Test if mouse cursor is hovering given rectangle
11521// NB- Rectangle is clipped by our current clip setting
11522// NB- Expand the rectangle to be generous on imprecise inputs systems (g.Style.TouchExtraPadding)
11523bool ImGui::IsMouseHoveringRect(const ImVec2 &r_min, const ImVec2 &r_max, bool clip)
11524{
11525 ImGuiContext &g = *GImGui;
11526
11527 // Clip
11528 ImRect rect_clipped(r_min, r_max);
11529 if (clip)
11530 rect_clipped.ClipWith(g.CurrentWindow->ClipRect);
11531
11532 // Hit testing, expanded for touch input
11533 if (!rect_clipped.ContainsWithPad(g.IO.MousePos, g.Style.TouchExtraPadding))
11534 return false;
11535 if (!g.MouseViewport->GetMainRect().Overlaps(rect_clipped))
11536 return false;
11537 return true;
11538}
11539
11540// Return if a mouse click/drag went past the given threshold. Valid to call during the MouseReleased frame.
11541// [Internal] This doesn't test if the button is pressed
11542bool ImGui::IsMouseDragPastThreshold(ImGuiMouseButton button, float lock_threshold)
11543{
11544 ImGuiContext &g = *GImGui;
11545 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
11546 if (lock_threshold < 0.0f)
11547 lock_threshold = g.IO.MouseDragThreshold;
11548 return g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold;
11549}
11550
11551bool ImGui::IsMouseDragging(ImGuiMouseButton button, float lock_threshold)
11552{
11553 ImGuiContext &g = *GImGui;
11554 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
11555 if (!g.IO.MouseDown[button])
11556 return false;
11557 return IsMouseDragPastThreshold(button, lock_threshold);
11558}
11559
11560ImVec2 ImGui::GetMousePos()
11561{
11562 ImGuiContext &g = *GImGui;
11563 return g.IO.MousePos;
11564}
11565
11566// This is called TeleportMousePos() and not SetMousePos() to emphasis that setting MousePosPrev will effectively clear
11567// mouse delta as well. It is expected you only call this if (io.BackendFlags & ImGuiBackendFlags_HasSetMousePos) is set
11568// and supported by backend.
11569void ImGui::TeleportMousePos(const ImVec2 &pos)
11570{
11571 ImGuiContext &g = *GImGui;
11572 g.IO.MousePos = g.IO.MousePosPrev = pos;
11573 g.IO.MouseDelta = ImVec2(0.0f, 0.0f);
11574 g.IO.WantSetMousePos = true;
11575 // IMGUI_DEBUG_LOG_IO("TeleportMousePos: (%.1f,%.1f)\n", io.MousePos.x, io.MousePos.y);
11576}
11577
11578// NB: prefer to call right after BeginPopup(). At the time Selectable/MenuItem is activated, the popup is already
11579// closed!
11580ImVec2 ImGui::GetMousePosOnOpeningCurrentPopup()
11581{
11582 ImGuiContext &g = *GImGui;
11583 if (g.BeginPopupStack.Size > 0)
11584 return g.OpenPopupStack[g.BeginPopupStack.Size - 1].OpenMousePos;
11585 return g.IO.MousePos;
11586}
11587
11588// We typically use ImVec2(-FLT_MAX,-FLT_MAX) to denote an invalid mouse position.
11589bool ImGui::IsMousePosValid(const ImVec2 *mouse_pos)
11590{
11591 // The assert is only to silence a false-positive in XCode Static Analysis.
11592 // Because GImGui is not dereferenced in every code path, the static analyzer assume that it may be NULL (which it
11593 // doesn't for other functions).
11594 IM_ASSERT(GImGui != NULL);
11595 const float MOUSE_INVALID = -256000.0f;
11596 ImVec2 p = mouse_pos ? *mouse_pos : GImGui->IO.MousePos;
11597 return p.x >= MOUSE_INVALID && p.y >= MOUSE_INVALID;
11598}
11599
11600// [WILL OBSOLETE] This was designed for backends, but prefer having backend maintain a mask of held mouse buttons,
11601// because upcoming input queue system will make this invalid.
11602bool ImGui::IsAnyMouseDown()
11603{
11604 ImGuiContext &g = *GImGui;
11605 for (int n = 0; n < IM_ARRAYSIZE(g.IO.MouseDown); n++)
11606 if (g.IO.MouseDown[n])
11607 return true;
11608 return false;
11609}
11610
11611// Return the delta from the initial clicking position while the mouse button is clicked or was just released.
11612// This is locked and return 0.0f until the mouse moves past a distance threshold at least once.
11613// NB: This is only valid if IsMousePosValid(). backends in theory should always keep mouse position valid when dragging
11614// even outside the client window.
11615ImVec2 ImGui::GetMouseDragDelta(ImGuiMouseButton button, float lock_threshold)
11616{
11617 ImGuiContext &g = *GImGui;
11618 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
11619 if (lock_threshold < 0.0f)
11620 lock_threshold = g.IO.MouseDragThreshold;
11621 if (g.IO.MouseDown[button] || g.IO.MouseReleased[button])
11622 if (g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold)
11623 if (IsMousePosValid(&g.IO.MousePos) && IsMousePosValid(&g.IO.MouseClickedPos[button]))
11624 return g.IO.MousePos - g.IO.MouseClickedPos[button];
11625 return ImVec2(0.0f, 0.0f);
11626}
11627
11628void ImGui::ResetMouseDragDelta(ImGuiMouseButton button)
11629{
11630 ImGuiContext &g = *GImGui;
11631 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
11632 // NB: We don't need to reset g.IO.MouseDragMaxDistanceSqr
11633 g.IO.MouseClickedPos[button] = g.IO.MousePos;
11634}
11635
11636// Get desired mouse cursor shape.
11637// Important: this is meant to be used by a platform backend, it is reset in ImGui::NewFrame(),
11638// updated during the frame, and locked in EndFrame()/Render().
11639// If you use software rendering by setting io.MouseDrawCursor then Dear ImGui will render those for you
11640ImGuiMouseCursor ImGui::GetMouseCursor()
11641{
11642 ImGuiContext &g = *GImGui;
11643 return g.MouseCursor;
11644}
11645
11646// We intentionally accept values of ImGuiMouseCursor that are outside our bounds, in case users needs to hack-in a
11647// custom cursor value. Custom cursors may be handled by custom backends. If you are using a standard backend and want
11648// to hack in a custom cursor, you may handle it before the backend _NewFrame() call and temporarily set
11649// ImGuiConfigFlags_NoMouseCursorChange during the backend _NewFrame() call.
11650void ImGui::SetMouseCursor(ImGuiMouseCursor cursor_type)
11651{
11652 ImGuiContext &g = *GImGui;
11653 g.MouseCursor = cursor_type;
11654}
11655
11656static void UpdateAliasKey(ImGuiKey key, bool v, float analog_value)
11657{
11658 IM_ASSERT(ImGui::IsAliasKey(key));
11659 ImGuiKeyData *key_data = ImGui::GetKeyData(key);
11660 key_data->Down = v;
11661 key_data->AnalogValue = analog_value;
11662}
11663
11664// [Internal] Do not use directly
11665static ImGuiKeyChord GetMergedModsFromKeys()
11666{
11667 ImGuiKeyChord mods = 0;
11668 if (ImGui::IsKeyDown(ImGuiMod_Ctrl))
11669 {
11670 mods |= ImGuiMod_Ctrl;
11671 }
11672 if (ImGui::IsKeyDown(ImGuiMod_Shift))
11673 {
11674 mods |= ImGuiMod_Shift;
11675 }
11676 if (ImGui::IsKeyDown(ImGuiMod_Alt))
11677 {
11678 mods |= ImGuiMod_Alt;
11679 }
11680 if (ImGui::IsKeyDown(ImGuiMod_Super))
11681 {
11682 mods |= ImGuiMod_Super;
11683 }
11684 return mods;
11685}
11686
11687static void ImGui::UpdateKeyboardInputs()
11688{
11689 ImGuiContext &g = *GImGui;
11690 ImGuiIO &io = g.IO;
11691
11692 if (io.ConfigFlags & ImGuiConfigFlags_NoKeyboard)
11693 io.ClearInputKeys();
11694
11695 // Update aliases
11696 for (int n = 0; n < ImGuiMouseButton_COUNT; n++)
11697 UpdateAliasKey(MouseButtonToKey(n), io.MouseDown[n], io.MouseDown[n] ? 1.0f : 0.0f);
11698 UpdateAliasKey(ImGuiKey_MouseWheelX, io.MouseWheelH != 0.0f, io.MouseWheelH);
11699 UpdateAliasKey(ImGuiKey_MouseWheelY, io.MouseWheel != 0.0f, io.MouseWheel);
11700
11701 // Synchronize io.KeyMods and io.KeyCtrl/io.KeyShift/etc. values.
11702 // - New backends (1.87+): send io.AddKeyEvent(ImGuiMod_XXX) -> -> (here)
11703 // deriving io.KeyMods + io.KeyXXX from key array.
11704 // - Legacy backends: set io.KeyXXX bools -> (above) set key array from io.KeyXXX -> (here)
11705 // deriving io.KeyMods + io.KeyXXX from key array. So with legacy backends the 4 values will do a unnecessary
11706 // back-and-forth but it makes the code simpler and future facing.
11707 const ImGuiKeyChord prev_key_mods = io.KeyMods;
11708 io.KeyMods = GetMergedModsFromKeys();
11709 io.KeyCtrl = (io.KeyMods & ImGuiMod_Ctrl) != 0;
11710 io.KeyShift = (io.KeyMods & ImGuiMod_Shift) != 0;
11711 io.KeyAlt = (io.KeyMods & ImGuiMod_Alt) != 0;
11712 io.KeySuper = (io.KeyMods & ImGuiMod_Super) != 0;
11713 if (prev_key_mods != io.KeyMods)
11714 g.LastKeyModsChangeTime = g.Time;
11715 if (prev_key_mods != io.KeyMods && prev_key_mods == 0)
11716 g.LastKeyModsChangeFromNoneTime = g.Time;
11717
11718 // Clear gamepad data if disabled
11719 if ((io.BackendFlags & ImGuiBackendFlags_HasGamepad) == 0)
11720 for (int key = ImGuiKey_Gamepad_BEGIN; key < ImGuiKey_Gamepad_END; key++)
11721 {
11722 io.KeysData[key - ImGuiKey_NamedKey_BEGIN].Down = false;
11723 io.KeysData[key - ImGuiKey_NamedKey_BEGIN].AnalogValue = 0.0f;
11724 }
11725
11726 // Update keys
11727 for (int key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key++)
11728 {
11729 ImGuiKeyData *key_data = &io.KeysData[key - ImGuiKey_NamedKey_BEGIN];
11730 key_data->DownDurationPrev = key_data->DownDuration;
11731 key_data->DownDuration =
11732 key_data->Down ? (key_data->DownDuration < 0.0f ? 0.0f : key_data->DownDuration + io.DeltaTime) : -1.0f;
11733 if (key_data->DownDuration == 0.0f)
11734 {
11735 if (IsKeyboardKey((ImGuiKey)key))
11736 g.LastKeyboardKeyPressTime = g.Time;
11737 else if (key == ImGuiKey_ReservedForModCtrl || key == ImGuiKey_ReservedForModShift ||
11738 key == ImGuiKey_ReservedForModAlt || key == ImGuiKey_ReservedForModSuper)
11739 g.LastKeyboardKeyPressTime = g.Time;
11740 }
11741 }
11742
11743 // Update keys/input owner (named keys only): one entry per key
11744 for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1))
11745 {
11746 ImGuiKeyData *key_data = &io.KeysData[key - ImGuiKey_NamedKey_BEGIN];
11747 ImGuiKeyOwnerData *owner_data = &g.KeysOwnerData[key - ImGuiKey_NamedKey_BEGIN];
11748 owner_data->OwnerCurr = owner_data->OwnerNext;
11749 if (!key_data->Down) // Important: ownership is released on the frame after a release. Ensure a 'MouseDown ->
11750 // CloseWindow -> MouseUp' chain doesn't lead to someone else seeing the MouseUp.
11751 owner_data->OwnerNext = ImGuiKeyOwner_NoOwner;
11752 owner_data->LockThisFrame = owner_data->LockUntilRelease =
11753 owner_data->LockUntilRelease && key_data->Down; // Clear LockUntilRelease when key is not Down anymore
11754 }
11755
11756 // Update key routing (for e.g. shortcuts)
11757 UpdateKeyRoutingTable(&g.KeysRoutingTable);
11758}
11759
11760static void ImGui::UpdateMouseInputs()
11761{
11762 ImGuiContext &g = *GImGui;
11763 ImGuiIO &io = g.IO;
11764
11765 // Mouse Wheel swapping flag
11766 // As a standard behavior holding SHIFT while using Vertical Mouse Wheel triggers Horizontal scroll instead
11767 // - We avoid doing it on OSX as it the OS input layer handles this already.
11768 // - FIXME: However this means when running on OSX over Emscripten, Shift+WheelY will incur two swapping (1 in OS, 1
11769 // here), canceling the feature.
11770 // - FIXME: When we can distinguish e.g. touchpad scroll events from mouse ones, we'll set this accordingly based on
11771 // input source.
11772 io.MouseWheelRequestAxisSwap = io.KeyShift && !io.ConfigMacOSXBehaviors;
11773
11774 // Round mouse position to avoid spreading non-rounded position (e.g. UpdateManualResize doesn't support them well)
11775 if (IsMousePosValid(&io.MousePos))
11776 io.MousePos = g.MouseLastValidPos = ImFloor(io.MousePos);
11777
11778 // If mouse just appeared or disappeared (usually denoted by -FLT_MAX components) we cancel out movement in
11779 // MouseDelta
11780 if (IsMousePosValid(&io.MousePos) && IsMousePosValid(&io.MousePosPrev))
11781 io.MouseDelta = io.MousePos - io.MousePosPrev;
11782 else
11783 io.MouseDelta = ImVec2(0.0f, 0.0f);
11784
11785 // Update stationary timer.
11786 // FIXME: May need to rework again to have some tolerance for occasional small movement, while being functional on
11787 // high-framerates.
11788 const float mouse_stationary_threshold =
11789 (io.MouseSource == ImGuiMouseSource_Mouse)
11790 ? 2.0f
11791 : 3.0f; // Slightly higher threshold for ImGuiMouseSource_TouchScreen/ImGuiMouseSource_Pen, may need rework.
11792 const bool mouse_stationary =
11793 (ImLengthSqr(io.MouseDelta) <= mouse_stationary_threshold * mouse_stationary_threshold);
11794 g.MouseStationaryTimer = mouse_stationary ? (g.MouseStationaryTimer + io.DeltaTime) : 0.0f;
11795 // IMGUI_DEBUG_LOG("%.4f\n", g.MouseStationaryTimer);
11796
11797 // If mouse moved we re-enable mouse hovering in case it was disabled by keyboard/gamepad. In theory should use a
11798 // >0.0f threshold but would need to reset in everywhere we set this to true.
11799 if (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f)
11800 g.NavHighlightItemUnderNav = false;
11801
11802 for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++)
11803 {
11804 io.MouseClicked[i] = io.MouseDown[i] && io.MouseDownDuration[i] < 0.0f;
11805 io.MouseClickedCount[i] = 0; // Will be filled below
11806 io.MouseReleased[i] = !io.MouseDown[i] && io.MouseDownDuration[i] >= 0.0f;
11807 if (io.MouseReleased[i])
11808 io.MouseReleasedTime[i] = g.Time;
11809 io.MouseDownDurationPrev[i] = io.MouseDownDuration[i];
11810 io.MouseDownDuration[i] =
11811 io.MouseDown[i] ? (io.MouseDownDuration[i] < 0.0f ? 0.0f : io.MouseDownDuration[i] + io.DeltaTime) : -1.0f;
11812 if (io.MouseClicked[i])
11813 {
11814 bool is_repeated_click = false;
11815 if ((float)(g.Time - io.MouseClickedTime[i]) < io.MouseDoubleClickTime)
11816 {
11817 ImVec2 delta_from_click_pos =
11818 IsMousePosValid(&io.MousePos) ? (io.MousePos - io.MouseClickedPos[i]) : ImVec2(0.0f, 0.0f);
11819 if (ImLengthSqr(delta_from_click_pos) < io.MouseDoubleClickMaxDist * io.MouseDoubleClickMaxDist)
11820 is_repeated_click = true;
11821 }
11822 if (is_repeated_click)
11823 io.MouseClickedLastCount[i]++;
11824 else
11825 io.MouseClickedLastCount[i] = 1;
11826 io.MouseClickedTime[i] = g.Time;
11827 io.MouseClickedPos[i] = io.MousePos;
11828 io.MouseClickedCount[i] = io.MouseClickedLastCount[i];
11829 io.MouseDragMaxDistanceAbs[i] = ImVec2(0.0f, 0.0f);
11830 io.MouseDragMaxDistanceSqr[i] = 0.0f;
11831 }
11832 else if (io.MouseDown[i])
11833 {
11834 // Maintain the maximum distance we reaching from the initial click position, which is used with dragging
11835 // threshold
11836 ImVec2 delta_from_click_pos =
11837 IsMousePosValid(&io.MousePos) ? (io.MousePos - io.MouseClickedPos[i]) : ImVec2(0.0f, 0.0f);
11838 io.MouseDragMaxDistanceSqr[i] = ImMax(io.MouseDragMaxDistanceSqr[i], ImLengthSqr(delta_from_click_pos));
11839 io.MouseDragMaxDistanceAbs[i].x =
11840 ImMax(io.MouseDragMaxDistanceAbs[i].x,
11841 delta_from_click_pos.x < 0.0f ? -delta_from_click_pos.x : delta_from_click_pos.x);
11842 io.MouseDragMaxDistanceAbs[i].y =
11843 ImMax(io.MouseDragMaxDistanceAbs[i].y,
11844 delta_from_click_pos.y < 0.0f ? -delta_from_click_pos.y : delta_from_click_pos.y);
11845 }
11846
11847 // We provide io.MouseDoubleClicked[] as a legacy service
11848 io.MouseDoubleClicked[i] = (io.MouseClickedCount[i] == 2);
11849
11850 // Clicking any mouse button reactivate mouse hovering which may have been deactivated by keyboard/gamepad
11851 // navigation
11852 if (io.MouseClicked[i])
11853 g.NavHighlightItemUnderNav = false;
11854 }
11855}
11856
11857static void LockWheelingWindow(ImGuiWindow *window, float wheel_amount)
11858{
11859 ImGuiContext &g = *GImGui;
11860 if (window)
11861 g.WheelingWindowReleaseTimer =
11862 ImMin(g.WheelingWindowReleaseTimer + ImAbs(wheel_amount) * WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER,
11863 WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER);
11864 else
11865 g.WheelingWindowReleaseTimer = 0.0f;
11866 if (g.WheelingWindow == window)
11867 return;
11868 IMGUI_DEBUG_LOG_IO("[io] LockWheelingWindow() \"%s\"\n", window ? window->Name : "NULL");
11869 g.WheelingWindow = window;
11870 g.WheelingWindowRefMousePos = g.IO.MousePos;
11871 if (window == NULL)
11872 {
11873 g.WheelingWindowStartFrame = -1;
11874 g.WheelingAxisAvg = ImVec2(0.0f, 0.0f);
11875 }
11876}
11877
11878static ImGuiWindow *FindBestWheelingWindow(const ImVec2 &wheel)
11879{
11880 // For each axis, find window in the hierarchy that may want to use scrolling
11881 ImGuiContext &g = *GImGui;
11882 ImGuiWindow *windows[2] = {NULL, NULL};
11883 for (int axis = 0; axis < 2; axis++)
11884 if (wheel[axis] != 0.0f)
11885 for (ImGuiWindow *window = windows[axis] = g.HoveredWindow; window->Flags & ImGuiWindowFlags_ChildWindow;
11886 window = windows[axis] = window->ParentWindow)
11887 {
11888 // Bubble up into parent window if:
11889 // - a child window doesn't allow any scrolling.
11890 // - a child window has the ImGuiWindowFlags_NoScrollWithMouse flag.
11893 const bool has_scrolling = (window->ScrollMax[axis] != 0.0f);
11894 const bool inputs_disabled = (window->Flags & ImGuiWindowFlags_NoScrollWithMouse) &&
11895 !(window->Flags & ImGuiWindowFlags_NoMouseInputs);
11896 // const bool scrolling_past_limits = (wheel_v < 0.0f) ? (window->Scroll[axis] <= 0.0f) :
11897 // (window->Scroll[axis] >= window->ScrollMax[axis]);
11898 if (has_scrolling && !inputs_disabled) // && !scrolling_past_limits)
11899 break; // select this window
11900 }
11901 if (windows[0] == NULL && windows[1] == NULL)
11902 return NULL;
11903
11904 // If there's only one window or only one axis then there's no ambiguity
11905 if (windows[0] == windows[1] || windows[0] == NULL || windows[1] == NULL)
11906 return windows[1] ? windows[1] : windows[0];
11907
11908 // If candidate are different windows we need to decide which one to prioritize
11909 // - First frame: only find a winner if one axis is zero.
11910 // - Subsequent frames: only find a winner when one is more than the other.
11911 if (g.WheelingWindowStartFrame == -1)
11912 g.WheelingWindowStartFrame = g.FrameCount;
11913 if ((g.WheelingWindowStartFrame == g.FrameCount && wheel.x != 0.0f && wheel.y != 0.0f) ||
11914 (g.WheelingAxisAvg.x == g.WheelingAxisAvg.y))
11915 {
11916 g.WheelingWindowWheelRemainder = wheel;
11917 return NULL;
11918 }
11919 return (g.WheelingAxisAvg.x > g.WheelingAxisAvg.y) ? windows[0] : windows[1];
11920}
11921
11922// Called by NewFrame()
11923void ImGui::UpdateMouseWheel()
11924{
11925 // Reset the locked window if we move the mouse or after the timer elapses.
11926 // FIXME: Ideally we could refactor to have one timer for "changing window w/ same axis" and a shorter timer for
11927 // "changing window or axis w/ other axis" (#3795)
11928 ImGuiContext &g = *GImGui;
11929 if (g.WheelingWindow != NULL)
11930 {
11931 g.WheelingWindowReleaseTimer -= g.IO.DeltaTime;
11932 if (IsMousePosValid() && ImLengthSqr(g.IO.MousePos - g.WheelingWindowRefMousePos) >
11933 g.IO.MouseDragThreshold * g.IO.MouseDragThreshold)
11934 g.WheelingWindowReleaseTimer = 0.0f;
11935 if (g.WheelingWindowReleaseTimer <= 0.0f)
11936 LockWheelingWindow(NULL, 0.0f);
11937 }
11938
11939 ImVec2 wheel;
11940 wheel.x = TestKeyOwner(ImGuiKey_MouseWheelX, ImGuiKeyOwner_NoOwner) ? g.IO.MouseWheelH : 0.0f;
11941 wheel.y = TestKeyOwner(ImGuiKey_MouseWheelY, ImGuiKeyOwner_NoOwner) ? g.IO.MouseWheel : 0.0f;
11942
11943 // IMGUI_DEBUG_LOG("MouseWheel X:%.3f Y:%.3f\n", wheel_x, wheel_y);
11944 ImGuiWindow *mouse_window = g.WheelingWindow ? g.WheelingWindow : g.HoveredWindow;
11945 if (!mouse_window || mouse_window->Collapsed)
11946 return;
11947
11948 // Zoom / Scale window
11949 // FIXME-OBSOLETE: This is an old feature, it still works but pretty much nobody is using it and may be best
11950 // redesigned.
11951 if (wheel.y != 0.0f && g.IO.KeyCtrl && g.IO.FontAllowUserScaling)
11952 {
11953 LockWheelingWindow(mouse_window, wheel.y);
11954 ImGuiWindow *window = mouse_window;
11955 const float new_font_scale = ImClamp(window->FontWindowScale + g.IO.MouseWheel * 0.10f, 0.50f, 2.50f);
11956 const float scale = new_font_scale / window->FontWindowScale;
11957 window->FontWindowScale = new_font_scale;
11958 if (window == window->RootWindow)
11959 {
11960 const ImVec2 offset = window->Size * (1.0f - scale) * (g.IO.MousePos - window->Pos) / window->Size;
11961 SetWindowPos(window, window->Pos + offset, 0);
11962 window->Size = ImTrunc(window->Size * scale);
11963 window->SizeFull = ImTrunc(window->SizeFull * scale);
11964 }
11965 return;
11966 }
11967 if (g.IO.KeyCtrl)
11968 return;
11969
11970 // Mouse wheel scrolling
11971 // Read about io.MouseWheelRequestAxisSwap and its issue on Mac+Emscripten in UpdateMouseInputs()
11972 if (g.IO.MouseWheelRequestAxisSwap)
11973 wheel = ImVec2(wheel.y, 0.0f);
11974
11975 // Maintain a rough average of moving magnitude on both axes
11976 // FIXME: should by based on wall clock time rather than frame-counter
11977 g.WheelingAxisAvg.x = ImExponentialMovingAverage(g.WheelingAxisAvg.x, ImAbs(wheel.x), 30);
11978 g.WheelingAxisAvg.y = ImExponentialMovingAverage(g.WheelingAxisAvg.y, ImAbs(wheel.y), 30);
11979
11980 // In the rare situation where FindBestWheelingWindow() had to defer first frame of wheeling due to ambiguous main
11981 // axis, reinject it now.
11982 wheel += g.WheelingWindowWheelRemainder;
11983 g.WheelingWindowWheelRemainder = ImVec2(0.0f, 0.0f);
11984 if (wheel.x == 0.0f && wheel.y == 0.0f)
11985 return;
11986
11987 // Mouse wheel scrolling: find target and apply
11988 // - don't renew lock if axis doesn't apply on the window.
11989 // - select a main axis when both axes are being moved.
11990 if (ImGuiWindow *window = (g.WheelingWindow ? g.WheelingWindow : FindBestWheelingWindow(wheel)))
11991 if (!(window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs))
11992 {
11993 bool do_scroll[2] = {wheel.x != 0.0f && window->ScrollMax.x != 0.0f,
11994 wheel.y != 0.0f && window->ScrollMax.y != 0.0f};
11995 if (do_scroll[ImGuiAxis_X] && do_scroll[ImGuiAxis_Y])
11996 do_scroll[(g.WheelingAxisAvg.x > g.WheelingAxisAvg.y) ? ImGuiAxis_Y : ImGuiAxis_X] = false;
11997 if (do_scroll[ImGuiAxis_X])
11998 {
11999 LockWheelingWindow(window, wheel.x);
12000 float max_step = window->InnerRect.GetWidth() * 0.67f;
12001 float scroll_step = ImTrunc(ImMin(2 * window->FontRefSize, max_step));
12002 SetScrollX(window, window->Scroll.x - wheel.x * scroll_step);
12003 g.WheelingWindowScrolledFrame = g.FrameCount;
12004 }
12005 if (do_scroll[ImGuiAxis_Y])
12006 {
12007 LockWheelingWindow(window, wheel.y);
12008 float max_step = window->InnerRect.GetHeight() * 0.67f;
12009 float scroll_step = ImTrunc(ImMin(5 * window->FontRefSize, max_step));
12010 SetScrollY(window, window->Scroll.y - wheel.y * scroll_step);
12011 g.WheelingWindowScrolledFrame = g.FrameCount;
12012 }
12013 }
12014}
12015
12016void ImGui::SetNextFrameWantCaptureKeyboard(bool want_capture_keyboard)
12017{
12018 ImGuiContext &g = *GImGui;
12019 g.WantCaptureKeyboardNextFrame = want_capture_keyboard ? 1 : 0;
12020}
12021
12022void ImGui::SetNextFrameWantCaptureMouse(bool want_capture_mouse)
12023{
12024 ImGuiContext &g = *GImGui;
12025 g.WantCaptureMouseNextFrame = want_capture_mouse ? 1 : 0;
12026}
12027
12028#ifndef IMGUI_DISABLE_DEBUG_TOOLS
12029static const char *GetInputSourceName(ImGuiInputSource source)
12030{
12031 const char *input_source_names[] = {"None", "Mouse", "Keyboard", "Gamepad"};
12032 IM_ASSERT(IM_ARRAYSIZE(input_source_names) == ImGuiInputSource_COUNT && source >= 0 &&
12033 source < ImGuiInputSource_COUNT);
12034 return input_source_names[source];
12035}
12036static const char *GetMouseSourceName(ImGuiMouseSource source)
12037{
12038 const char *mouse_source_names[] = {"Mouse", "TouchScreen", "Pen"};
12039 IM_ASSERT(IM_ARRAYSIZE(mouse_source_names) == ImGuiMouseSource_COUNT && source >= 0 &&
12040 source < ImGuiMouseSource_COUNT);
12041 return mouse_source_names[source];
12042}
12043static void DebugPrintInputEvent(const char *prefix, const ImGuiInputEvent *e)
12044{
12045 ImGuiContext &g = *GImGui;
12046 if (e->Type == ImGuiInputEventType_MousePos)
12047 {
12048 if (e->MousePos.PosX == -FLT_MAX && e->MousePos.PosY == -FLT_MAX)
12049 IMGUI_DEBUG_LOG_IO("[io] %s: MousePos (-FLT_MAX, -FLT_MAX)\n", prefix);
12050 else
12051 IMGUI_DEBUG_LOG_IO("[io] %s: MousePos (%.1f, %.1f) (%s)\n", prefix, e->MousePos.PosX, e->MousePos.PosY,
12052 GetMouseSourceName(e->MousePos.MouseSource));
12053 return;
12054 }
12055 if (e->Type == ImGuiInputEventType_MouseButton)
12056 {
12057 IMGUI_DEBUG_LOG_IO("[io] %s: MouseButton %d %s (%s)\n", prefix, e->MouseButton.Button,
12058 e->MouseButton.Down ? "Down" : "Up", GetMouseSourceName(e->MouseButton.MouseSource));
12059 return;
12060 }
12061 if (e->Type == ImGuiInputEventType_MouseWheel)
12062 {
12063 IMGUI_DEBUG_LOG_IO("[io] %s: MouseWheel (%.3f, %.3f) (%s)\n", prefix, e->MouseWheel.WheelX,
12064 e->MouseWheel.WheelY, GetMouseSourceName(e->MouseWheel.MouseSource));
12065 return;
12066 }
12067 if (e->Type == ImGuiInputEventType_MouseViewport)
12068 {
12069 IMGUI_DEBUG_LOG_IO("[io] %s: MouseViewport (0x%08X)\n", prefix, e->MouseViewport.HoveredViewportID);
12070 return;
12071 }
12072 if (e->Type == ImGuiInputEventType_Key)
12073 {
12074 IMGUI_DEBUG_LOG_IO("[io] %s: Key \"%s\" %s\n", prefix, ImGui::GetKeyName(e->Key.Key),
12075 e->Key.Down ? "Down" : "Up");
12076 return;
12077 }
12078 if (e->Type == ImGuiInputEventType_Text)
12079 {
12080 IMGUI_DEBUG_LOG_IO("[io] %s: Text: %c (U+%08X)\n", prefix, e->Text.Char, e->Text.Char);
12081 return;
12082 }
12083 if (e->Type == ImGuiInputEventType_Focus)
12084 {
12085 IMGUI_DEBUG_LOG_IO("[io] %s: AppFocused %d\n", prefix, e->AppFocused.Focused);
12086 return;
12087 }
12088}
12089#endif
12090
12091// Process input queue
12092// We always call this with the value of 'bool g.IO.ConfigInputTrickleEventQueue'.
12093// - trickle_fast_inputs = false : process all events, turn into flattened input state (e.g. successive down/up/down/up
12094// will be lost)
12095// - trickle_fast_inputs = true : process as many events as possible (successive down/up/down/up will be trickled over
12096// several frames so nothing is lost) (new feature in 1.87)
12097void ImGui::UpdateInputEvents(bool trickle_fast_inputs)
12098{
12099 ImGuiContext &g = *GImGui;
12100 ImGuiIO &io = g.IO;
12101
12102 // Only trickle chars<>key when working with InputText()
12103 // FIXME: InputText() could parse event trail?
12104 // FIXME: Could specialize chars<>keys trickling rules for control keys (those not typically associated to
12105 // characters)
12106 const bool trickle_interleaved_nonchar_keys_and_text = (trickle_fast_inputs && g.WantTextInputNextFrame == 1);
12107
12108 bool mouse_moved = false, mouse_wheeled = false, key_changed = false, key_changed_nonchar = false,
12109 text_inputted = false;
12110 int mouse_button_changed = 0x00;
12111 ImBitArray<ImGuiKey_NamedKey_COUNT> key_changed_mask;
12112
12113 int event_n = 0;
12114 for (; event_n < g.InputEventsQueue.Size; event_n++)
12115 {
12116 ImGuiInputEvent *e = &g.InputEventsQueue[event_n];
12117 if (e->Type == ImGuiInputEventType_MousePos)
12118 {
12119 if (g.IO.WantSetMousePos)
12120 continue;
12121 // Trickling Rule: Stop processing queued events if we already handled a mouse button change
12122 ImVec2 event_pos(e->MousePos.PosX, e->MousePos.PosY);
12123 if (trickle_fast_inputs && (mouse_button_changed != 0 || mouse_wheeled || key_changed || text_inputted))
12124 break;
12125 io.MousePos = event_pos;
12126 io.MouseSource = e->MousePos.MouseSource;
12127 mouse_moved = true;
12128 }
12129 else if (e->Type == ImGuiInputEventType_MouseButton)
12130 {
12131 // Trickling Rule: Stop processing queued events if we got multiple action on the same button
12132 const ImGuiMouseButton button = e->MouseButton.Button;
12133 IM_ASSERT(button >= 0 && button < ImGuiMouseButton_COUNT);
12134 if (trickle_fast_inputs && ((mouse_button_changed & (1 << button)) || mouse_wheeled))
12135 break;
12136 if (trickle_fast_inputs && e->MouseButton.MouseSource == ImGuiMouseSource_TouchScreen &&
12137 mouse_moved) // #2702: TouchScreen have no initial hover.
12138 break;
12139 io.MouseDown[button] = e->MouseButton.Down;
12140 io.MouseSource = e->MouseButton.MouseSource;
12141 mouse_button_changed |= (1 << button);
12142 }
12143 else if (e->Type == ImGuiInputEventType_MouseWheel)
12144 {
12145 // Trickling Rule: Stop processing queued events if we got multiple action on the event
12146 if (trickle_fast_inputs && (mouse_moved || mouse_button_changed != 0))
12147 break;
12148 io.MouseWheelH += e->MouseWheel.WheelX;
12149 io.MouseWheel += e->MouseWheel.WheelY;
12150 io.MouseSource = e->MouseWheel.MouseSource;
12151 mouse_wheeled = true;
12152 }
12153 else if (e->Type == ImGuiInputEventType_MouseViewport)
12154 {
12155 io.MouseHoveredViewport = e->MouseViewport.HoveredViewportID;
12156 }
12157 else if (e->Type == ImGuiInputEventType_Key)
12158 {
12159 // Trickling Rule: Stop processing queued events if we got multiple action on the same button
12160 if (io.ConfigFlags & ImGuiConfigFlags_NoKeyboard)
12161 continue;
12162 ImGuiKey key = e->Key.Key;
12163 IM_ASSERT(key != ImGuiKey_None);
12164 ImGuiKeyData *key_data = GetKeyData(key);
12165 const int key_data_index = (int)(key_data - g.IO.KeysData);
12166 if (trickle_fast_inputs && key_data->Down != e->Key.Down &&
12167 (key_changed_mask.TestBit(key_data_index) || mouse_button_changed != 0))
12168 break;
12169
12170 const bool key_is_potentially_for_char_input =
12171 IsKeyChordPotentiallyCharInput(GetMergedModsFromKeys() | key);
12172 if (trickle_interleaved_nonchar_keys_and_text && (text_inputted && !key_is_potentially_for_char_input))
12173 break;
12174
12175 if (key_data->Down != e->Key.Down) // Analog change only do not trigger this, so it won't block e.g. further
12176 // mouse pos events testing key_changed.
12177 {
12178 key_changed = true;
12179 key_changed_mask.SetBit(key_data_index);
12180 if (trickle_interleaved_nonchar_keys_and_text && !key_is_potentially_for_char_input)
12181 key_changed_nonchar = true;
12182 }
12183
12184 key_data->Down = e->Key.Down;
12185 key_data->AnalogValue = e->Key.AnalogValue;
12186 }
12187 else if (e->Type == ImGuiInputEventType_Text)
12188 {
12189 if (io.ConfigFlags & ImGuiConfigFlags_NoKeyboard)
12190 continue;
12191 // Trickling Rule: Stop processing queued events if keys/mouse have been interacted with
12192 if (trickle_fast_inputs && (mouse_button_changed != 0 || mouse_moved || mouse_wheeled))
12193 break;
12194 if (trickle_interleaved_nonchar_keys_and_text && key_changed_nonchar)
12195 break;
12196 unsigned int c = e->Text.Char;
12197 io.InputQueueCharacters.push_back(c <= IM_UNICODE_CODEPOINT_MAX ? (ImWchar)c
12198 : IM_UNICODE_CODEPOINT_INVALID);
12199 if (trickle_interleaved_nonchar_keys_and_text)
12200 text_inputted = true;
12201 }
12202 else if (e->Type == ImGuiInputEventType_Focus)
12203 {
12204 // We intentionally overwrite this and process in NewFrame(), in order to give a chance
12205 // to multi-viewports backends to queue AddFocusEvent(false) + AddFocusEvent(true) in same frame.
12206 const bool focus_lost = !e->AppFocused.Focused;
12207 io.AppFocusLost = focus_lost;
12208 }
12209 else
12210 {
12211 IM_ASSERT(0 && "Unknown event!");
12212 }
12213 }
12214
12215 // Record trail (for domain-specific applications wanting to access a precise trail)
12216 // if (event_n != 0) IMGUI_DEBUG_LOG_IO("Processed: %d / Remaining: %d\n", event_n, g.InputEventsQueue.Size -
12217 // event_n);
12218 for (int n = 0; n < event_n; n++)
12219 g.InputEventsTrail.push_back(g.InputEventsQueue[n]);
12220
12221 // [DEBUG]
12222#ifndef IMGUI_DISABLE_DEBUG_TOOLS
12223 if (event_n != 0 && (g.DebugLogFlags & ImGuiDebugLogFlags_EventIO))
12224 for (int n = 0; n < g.InputEventsQueue.Size; n++)
12225 DebugPrintInputEvent(n < event_n ? "Processed" : "Remaining", &g.InputEventsQueue[n]);
12226#endif
12227
12228 // Remaining events will be processed on the next frame
12229 if (event_n == g.InputEventsQueue.Size)
12230 g.InputEventsQueue.resize(0);
12231 else
12232 g.InputEventsQueue.erase(g.InputEventsQueue.Data, g.InputEventsQueue.Data + event_n);
12233
12234 // Clear buttons state when focus is lost
12235 // - this is useful so e.g. releasing Alt after focus loss on Alt-Tab doesn't trigger the Alt menu toggle.
12236 // - we clear in EndFrame() and not now in order allow application/user code polling this flag
12237 // (e.g. custom backend may want to clear additional data, custom widgets may want to react with a "canceling"
12238 // event).
12239 if (g.IO.AppFocusLost)
12240 {
12241 g.IO.ClearInputKeys();
12242 g.IO.ClearInputMouse();
12243 }
12244}
12245
12246ImGuiID ImGui::GetKeyOwner(ImGuiKey key)
12247{
12248 if (!IsNamedKeyOrMod(key))
12249 return ImGuiKeyOwner_NoOwner;
12250
12251 ImGuiContext &g = *GImGui;
12252 ImGuiKeyOwnerData *owner_data = GetKeyOwnerData(&g, key);
12253 ImGuiID owner_id = owner_data->OwnerCurr;
12254
12255 if (g.ActiveIdUsingAllKeyboardKeys && owner_id != g.ActiveId && owner_id != ImGuiKeyOwner_Any)
12256 if (key >= ImGuiKey_Keyboard_BEGIN && key < ImGuiKey_Keyboard_END)
12257 return ImGuiKeyOwner_NoOwner;
12258
12259 return owner_id;
12260}
12261
12262// TestKeyOwner(..., ID) : (owner == None || owner == ID)
12263// TestKeyOwner(..., None) : (owner == None)
12264// TestKeyOwner(..., Any) : no owner test
12265// All paths are also testing for key not being locked, for the rare cases that key have been locked with using
12266// ImGuiInputFlags_LockXXX flags.
12267bool ImGui::TestKeyOwner(ImGuiKey key, ImGuiID owner_id)
12268{
12269 if (!IsNamedKeyOrMod(key))
12270 return true;
12271
12272 ImGuiContext &g = *GImGui;
12273 if (g.ActiveIdUsingAllKeyboardKeys && owner_id != g.ActiveId && owner_id != ImGuiKeyOwner_Any)
12274 if (key >= ImGuiKey_Keyboard_BEGIN && key < ImGuiKey_Keyboard_END)
12275 return false;
12276
12277 ImGuiKeyOwnerData *owner_data = GetKeyOwnerData(&g, key);
12278 if (owner_id == ImGuiKeyOwner_Any)
12279 return (owner_data->LockThisFrame == false);
12280
12281 // Note: SetKeyOwner() sets OwnerCurr. It is not strictly required for most mouse routing overlap (because of
12282 // ActiveId/HoveredId are acting as filter before this has a chance to filter), but sane as soon as user tries to
12283 // look into things. Setting OwnerCurr in SetKeyOwner() is more consistent than testing OwnerNext here: would be
12284 // inconsistent with getter and other functions.
12285 if (owner_data->OwnerCurr != owner_id)
12286 {
12287 if (owner_data->LockThisFrame)
12288 return false;
12289 if (owner_data->OwnerCurr != ImGuiKeyOwner_NoOwner)
12290 return false;
12291 }
12292
12293 return true;
12294}
12295
12296// _LockXXX flags are useful to lock keys away from code which is not input-owner aware.
12297// When using _LockXXX flags, you can use ImGuiKeyOwner_Any to lock keys from everyone.
12298// - SetKeyOwner(..., None) : clears owner
12299// - SetKeyOwner(..., Any, !Lock) : illegal (assert)
12300// - SetKeyOwner(..., Any or None, Lock) : set lock
12301void ImGui::SetKeyOwner(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags)
12302{
12303 ImGuiContext &g = *GImGui;
12304 IM_ASSERT(IsNamedKeyOrMod(key) &&
12305 (owner_id != ImGuiKeyOwner_Any ||
12306 (flags & (ImGuiInputFlags_LockThisFrame |
12307 ImGuiInputFlags_LockUntilRelease)))); // Can only use _Any with _LockXXX flags (to eat a key
12308 // away without an ID to retrieve it)
12309 IM_ASSERT((flags & ~ImGuiInputFlags_SupportedBySetKeyOwner) == 0); // Passing flags not supported by this function!
12310 // IMGUI_DEBUG_LOG("SetKeyOwner(%s, owner_id=0x%08X, flags=%08X)\n", GetKeyName(key), owner_id, flags);
12311
12312 ImGuiKeyOwnerData *owner_data = GetKeyOwnerData(&g, key);
12313 owner_data->OwnerCurr = owner_data->OwnerNext = owner_id;
12314
12315 // We cannot lock by default as it would likely break lots of legacy code.
12316 // In the case of using LockUntilRelease while key is not down we still lock during the frame (no key_data->Down
12317 // test)
12318 owner_data->LockUntilRelease = (flags & ImGuiInputFlags_LockUntilRelease) != 0;
12319 owner_data->LockThisFrame = (flags & ImGuiInputFlags_LockThisFrame) != 0 || (owner_data->LockUntilRelease);
12320}
12321
12322// Rarely used helper
12323void ImGui::SetKeyOwnersForKeyChord(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags)
12324{
12325 if (key_chord & ImGuiMod_Ctrl)
12326 {
12327 SetKeyOwner(ImGuiMod_Ctrl, owner_id, flags);
12328 }
12329 if (key_chord & ImGuiMod_Shift)
12330 {
12331 SetKeyOwner(ImGuiMod_Shift, owner_id, flags);
12332 }
12333 if (key_chord & ImGuiMod_Alt)
12334 {
12335 SetKeyOwner(ImGuiMod_Alt, owner_id, flags);
12336 }
12337 if (key_chord & ImGuiMod_Super)
12338 {
12339 SetKeyOwner(ImGuiMod_Super, owner_id, flags);
12340 }
12341 if (key_chord & ~ImGuiMod_Mask_)
12342 {
12343 SetKeyOwner((ImGuiKey)(key_chord & ~ImGuiMod_Mask_), owner_id, flags);
12344 }
12345}
12346
12347// This is more or less equivalent to:
12348// if (IsItemHovered() || IsItemActive())
12349// SetKeyOwner(key, GetItemID());
12350// Extensive uses of that (e.g. many calls for a single item) may want to manually perform the tests once and then call
12351// SetKeyOwner() multiple times. More advanced usage scenarios may want to call SetKeyOwner() manually based on
12352// different condition. Worth noting is that only one item can be hovered and only one item can be active, therefore
12353// this usage pattern doesn't need to bother with routing and priority.
12354void ImGui::SetItemKeyOwner(ImGuiKey key, ImGuiInputFlags flags)
12355{
12356 ImGuiContext &g = *GImGui;
12357 ImGuiID id = g.LastItemData.ID;
12358 if (id == 0 || (g.HoveredId != id && g.ActiveId != id))
12359 return;
12360 if ((flags & ImGuiInputFlags_CondMask_) == 0)
12361 flags |= ImGuiInputFlags_CondDefault_;
12362 if ((g.HoveredId == id && (flags & ImGuiInputFlags_CondHovered)) ||
12363 (g.ActiveId == id && (flags & ImGuiInputFlags_CondActive)))
12364 {
12365 IM_ASSERT((flags & ~ImGuiInputFlags_SupportedBySetItemKeyOwner) ==
12366 0); // Passing flags not supported by this function!
12367 SetKeyOwner(key, id, flags & ~ImGuiInputFlags_CondMask_);
12368 }
12369}
12370
12371void ImGui::SetItemKeyOwner(ImGuiKey key)
12372{
12373 SetItemKeyOwner(key, ImGuiInputFlags_None);
12374}
12375
12376// This is the only public API until we expose owner_id versions of the API as replacements.
12377bool ImGui::IsKeyChordPressed(ImGuiKeyChord key_chord)
12378{
12379 return IsKeyChordPressed(key_chord, ImGuiInputFlags_None, ImGuiKeyOwner_Any);
12380}
12381
12382// This is equivalent to comparing KeyMods + doing a IsKeyPressed()
12383bool ImGui::IsKeyChordPressed(ImGuiKeyChord key_chord, ImGuiInputFlags flags, ImGuiID owner_id)
12384{
12385 ImGuiContext &g = *GImGui;
12386 key_chord = FixupKeyChord(key_chord);
12387 ImGuiKey mods = (ImGuiKey)(key_chord & ImGuiMod_Mask_);
12388 if (g.IO.KeyMods != mods)
12389 return false;
12390
12391 // Special storage location for mods
12392 ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_);
12393 if (key == ImGuiKey_None)
12394 key = ConvertSingleModFlagToKey(mods);
12395 if (!IsKeyPressed(key, (flags & ImGuiInputFlags_RepeatMask_), owner_id))
12396 return false;
12397 return true;
12398}
12399
12400void ImGui::SetNextItemShortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags)
12401{
12402 ImGuiContext &g = *GImGui;
12403 g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasShortcut;
12404 g.NextItemData.Shortcut = key_chord;
12405 g.NextItemData.ShortcutFlags = flags;
12406}
12407
12408// Called from within ItemAdd: at this point we can read from NextItemData and write to LastItemData
12409void ImGui::ItemHandleShortcut(ImGuiID id)
12410{
12411 ImGuiContext &g = *GImGui;
12412 ImGuiInputFlags flags = g.NextItemData.ShortcutFlags;
12413 IM_ASSERT((flags & ~ImGuiInputFlags_SupportedBySetNextItemShortcut) ==
12414 0); // Passing flags not supported by SetNextItemShortcut()!
12415
12416 if (g.LastItemData.ItemFlags & ImGuiItemFlags_Disabled)
12417 return;
12418 if (flags & ImGuiInputFlags_Tooltip)
12419 {
12420 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasShortcut;
12421 g.LastItemData.Shortcut = g.NextItemData.Shortcut;
12422 }
12423 if (!Shortcut(g.NextItemData.Shortcut, flags & ImGuiInputFlags_SupportedByShortcut, id) || g.NavActivateId != 0)
12424 return;
12425
12426 // FIXME: Generalize Activation queue?
12427 g.NavActivateId = id; // Will effectively disable clipping.
12428 g.NavActivateFlags = ImGuiActivateFlags_PreferInput | ImGuiActivateFlags_FromShortcut;
12429 // if (g.ActiveId == 0 || g.ActiveId == id)
12430 g.NavActivateDownId = g.NavActivatePressedId = id;
12431 NavHighlightActivated(id);
12432}
12433
12434bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags)
12435{
12436 return Shortcut(key_chord, flags, ImGuiKeyOwner_Any);
12437}
12438
12439bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags, ImGuiID owner_id)
12440{
12441 ImGuiContext &g = *GImGui;
12442 // IMGUI_DEBUG_LOG("Shortcut(%s, flags=%X, owner_id=0x%08X)\n", GetKeyChordName(key_chord, g.TempBuffer.Data,
12443 // g.TempBuffer.Size), flags, owner_id);
12444
12445 // When using (owner_id == 0/Any): SetShortcutRouting() will use CurrentFocusScopeId and filter with this, so
12446 // IsKeyPressed() is fine with he 0/Any.
12447 if ((flags & ImGuiInputFlags_RouteTypeMask_) == 0)
12448 flags |= ImGuiInputFlags_RouteFocused;
12449
12450 // Using 'owner_id == ImGuiKeyOwner_Any/0': auto-assign an owner based on current focus scope (each window has its
12451 // focus scope by default) Effectively makes Shortcut() always input-owner aware.
12452 if (owner_id == ImGuiKeyOwner_Any || owner_id == ImGuiKeyOwner_NoOwner)
12453 owner_id = GetRoutingIdFromOwnerId(owner_id);
12454
12455 if (g.CurrentItemFlags & ImGuiItemFlags_Disabled)
12456 return false;
12457
12458 // Submit route
12459 if (!SetShortcutRouting(key_chord, flags, owner_id))
12460 return false;
12461
12462 // Default repeat behavior for Shortcut()
12463 // So e.g. pressing Ctrl+W and releasing Ctrl while holding W will not trigger the W shortcut.
12464 if ((flags & ImGuiInputFlags_Repeat) != 0 && (flags & ImGuiInputFlags_RepeatUntilMask_) == 0)
12465 flags |= ImGuiInputFlags_RepeatUntilKeyModsChange;
12466
12467 if (!IsKeyChordPressed(key_chord, flags, owner_id))
12468 return false;
12469
12470 // Claim mods during the press
12471 SetKeyOwnersForKeyChord(key_chord & ImGuiMod_Mask_, owner_id);
12472
12473 IM_ASSERT((flags & ~ImGuiInputFlags_SupportedByShortcut) == 0); // Passing flags not supported by this function!
12474 return true;
12475}
12476
12477//-----------------------------------------------------------------------------
12478// [SECTION] ERROR CHECKING, STATE RECOVERY
12479//-----------------------------------------------------------------------------
12480// - DebugCheckVersionAndDataLayout() (called via IMGUI_CHECKVERSION() macros)
12481// - ErrorCheckUsingSetCursorPosToExtendParentBoundaries()
12482// - ErrorCheckNewFrameSanityChecks()
12483// - ErrorCheckEndFrameSanityChecks()
12484// - ErrorRecoveryStoreState()
12485// - ErrorRecoveryTryToRecoverState()
12486// - ErrorRecoveryTryToRecoverWindowState()
12487// - ErrorLog()
12488//-----------------------------------------------------------------------------
12489
12490// Verify ABI compatibility between caller code and compiled version of Dear ImGui. This helps detects some build
12491// issues. Called by IMGUI_CHECKVERSION(). Verify that the type sizes are matching between the calling file's
12492// compilation unit and imgui.cpp's compilation unit If this triggers you have mismatched headers and compiled code
12493// versions.
12494// - It could be because of a build issue (using new headers with old compiled code)
12495// - It could be because of mismatched configuration #define, compilation settings, packing pragma etc.
12496// THE CONFIGURATION SETTINGS MENTIONED IN imconfig.h MUST BE SET FOR ALL COMPILATION UNITS INVOLVED WITH DEAR IMGUI.
12497// Which is why it is required you put them in your imconfig file (and NOT only before including imgui.h).
12498// Otherwise it is possible that different compilation units would see different structure layout.
12499// If you don't want to modify imconfig.h you can use the IMGUI_USER_CONFIG define to change filename.
12500bool ImGui::DebugCheckVersionAndDataLayout(const char *version, size_t sz_io, size_t sz_style, size_t sz_vec2,
12501 size_t sz_vec4, size_t sz_vert, size_t sz_idx)
12502{
12503 bool error = false;
12504 if (strcmp(version, IMGUI_VERSION) != 0)
12505 {
12506 error = true;
12507 IM_ASSERT(strcmp(version, IMGUI_VERSION) == 0 && "Mismatched version string!");
12508 }
12509 if (sz_io != sizeof(ImGuiIO))
12510 {
12511 error = true;
12512 IM_ASSERT(sz_io == sizeof(ImGuiIO) && "Mismatched struct layout!");
12513 }
12514 if (sz_style != sizeof(ImGuiStyle))
12515 {
12516 error = true;
12517 IM_ASSERT(sz_style == sizeof(ImGuiStyle) && "Mismatched struct layout!");
12518 }
12519 if (sz_vec2 != sizeof(ImVec2))
12520 {
12521 error = true;
12522 IM_ASSERT(sz_vec2 == sizeof(ImVec2) && "Mismatched struct layout!");
12523 }
12524 if (sz_vec4 != sizeof(ImVec4))
12525 {
12526 error = true;
12527 IM_ASSERT(sz_vec4 == sizeof(ImVec4) && "Mismatched struct layout!");
12528 }
12529 if (sz_vert != sizeof(ImDrawVert))
12530 {
12531 error = true;
12532 IM_ASSERT(sz_vert == sizeof(ImDrawVert) && "Mismatched struct layout!");
12533 }
12534 if (sz_idx != sizeof(ImDrawIdx))
12535 {
12536 error = true;
12537 IM_ASSERT(sz_idx == sizeof(ImDrawIdx) && "Mismatched struct layout!");
12538 }
12539 return !error;
12540}
12541
12542// Until 1.89 (IMGUI_VERSION_NUM < 18814) it was legal to use SetCursorPos() to extend the boundary of a parent (e.g.
12543// window or table cell) This is causing issues and ambiguity and we need to retire that. See
12544// https://github.com/ocornut/imgui/issues/5548 for more details. [Scenario 1]
12545// Previously this would make the window content size ~200x200:
12546// Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + End(); // NOT OK
12547// Instead, please submit an item:
12548// Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + Dummy(ImVec2(0,0)) + End(); // OK
12549// Alternative:
12550// Begin(...) + Dummy(ImVec2(200,200)) + End(); // OK
12551// [Scenario 2]
12552// For reference this is one of the issue what we aim to fix with this change:
12553// BeginGroup() + SomeItem("foobar") + SetCursorScreenPos(GetCursorScreenPos()) + EndGroup()
12554// The previous logic made SetCursorScreenPos(GetCursorScreenPos()) have a side-effect! It would erroneously
12555// incorporate ItemSpacing.y after the item into content size, making the group taller! While this code is a little
12556// twisted, no-one would expect SetXXX(GetXXX()) to have a side-effect. Using vertical alignment patterns could trigger
12557// this issue.
12558void ImGui::ErrorCheckUsingSetCursorPosToExtendParentBoundaries()
12559{
12560 ImGuiContext &g = *GImGui;
12561 ImGuiWindow *window = g.CurrentWindow;
12562 IM_ASSERT(window->DC.IsSetPos);
12563 window->DC.IsSetPos = false;
12564#ifdef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
12565 if (window->DC.CursorPos.x <= window->DC.CursorMaxPos.x && window->DC.CursorPos.y <= window->DC.CursorMaxPos.y)
12566 return;
12567 if (window->SkipItems)
12568 return;
12569 IM_ASSERT(0 && "Code uses SetCursorPos()/SetCursorScreenPos() to extend window/parent boundaries. Please submit an "
12570 "item e.g. Dummy() to validate extent.");
12571#else
12572 window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos);
12573#endif
12574}
12575
12576static void ImGui::ErrorCheckNewFrameSanityChecks()
12577{
12578 ImGuiContext &g = *GImGui;
12579
12580 // Check user IM_ASSERT macro
12581 // (IF YOU GET A WARNING OR COMPILE ERROR HERE: it means your assert macro is incorrectly defined!
12582 // If your macro uses multiple statements, it NEEDS to be surrounded by a 'do { ... } while (0)' block.
12583 // This is a common C/C++ idiom to allow multiple statements macros to be used in control flow blocks.)
12584 // #define IM_ASSERT(EXPR) if (SomeCode(EXPR)) SomeMoreCode(); // Wrong!
12585 // #define IM_ASSERT(EXPR) do { if (SomeCode(EXPR)) SomeMoreCode(); } while (0) // Correct!
12586 if (true)
12587 IM_ASSERT(1);
12588 else
12589 IM_ASSERT(0);
12590
12591 // Emscripten backends are often imprecise in their submission of DeltaTime. (#6114, #3644)
12592 // Ideally the Emscripten app/backend should aim to fix or smooth this value and avoid feeding zero, but we tolerate
12593 // it.
12594#ifdef __EMSCRIPTEN__
12595 if (g.IO.DeltaTime <= 0.0f && g.FrameCount > 0)
12596 g.IO.DeltaTime = 0.00001f;
12597#endif
12598
12599 // Check user data
12600 // (We pass an error message in the assert expression to make it visible to programmers who are not using a
12601 // debugger, as most assert handlers display their argument)
12602 IM_ASSERT(g.Initialized);
12603 IM_ASSERT((g.IO.DeltaTime > 0.0f || g.FrameCount == 0) && "Need a positive DeltaTime!");
12604 IM_ASSERT((g.FrameCount == 0 || g.FrameCountEnded == g.FrameCount) &&
12605 "Forgot to call Render() or EndFrame() at the end of the previous frame?");
12606 IM_ASSERT(g.IO.DisplaySize.x >= 0.0f && g.IO.DisplaySize.y >= 0.0f && "Invalid DisplaySize value!");
12607 IM_ASSERT(g.IO.Fonts->IsBuilt() &&
12608 "Font Atlas not built! Make sure you called ImGui_ImplXXXX_NewFrame() function for renderer backend, "
12609 "which should call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8()");
12610 IM_ASSERT(g.Style.CurveTessellationTol > 0.0f && "Invalid style setting!");
12611 IM_ASSERT(g.Style.CircleTessellationMaxError > 0.0f && "Invalid style setting!");
12612 IM_ASSERT(g.Style.Alpha >= 0.0f && g.Style.Alpha <= 1.0f &&
12613 "Invalid style setting!"); // Allows us to avoid a few clamps in color computations
12614 IM_ASSERT(g.Style.WindowMinSize.x >= 1.0f && g.Style.WindowMinSize.y >= 1.0f && "Invalid style setting!");
12615 IM_ASSERT(g.Style.WindowBorderHoverPadding > 0.0f &&
12616 "Invalid style setting!"); // Required otherwise cannot resize from borders.
12617 IM_ASSERT(g.Style.WindowMenuButtonPosition == ImGuiDir_None || g.Style.WindowMenuButtonPosition == ImGuiDir_Left ||
12618 g.Style.WindowMenuButtonPosition == ImGuiDir_Right);
12619 IM_ASSERT(g.Style.ColorButtonPosition == ImGuiDir_Left || g.Style.ColorButtonPosition == ImGuiDir_Right);
12620
12621 // Error handling: we do not accept 100% silent recovery! Please contact me if you feel this is getting in your way.
12622 if (g.IO.ConfigErrorRecovery)
12623 IM_ASSERT(g.IO.ConfigErrorRecoveryEnableAssert || g.IO.ConfigErrorRecoveryEnableDebugLog ||
12624 g.IO.ConfigErrorRecoveryEnableTooltip || g.ErrorCallback != NULL);
12625
12626#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
12627 // Remap legacy names
12628 if (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos)
12629 {
12630 g.IO.ConfigNavMoveSetMousePos = true;
12631 g.IO.ConfigFlags &= ~ImGuiConfigFlags_NavEnableSetMousePos;
12632 }
12633 if (g.IO.ConfigFlags & ImGuiConfigFlags_NavNoCaptureKeyboard)
12634 {
12635 g.IO.ConfigNavCaptureKeyboard = false;
12636 g.IO.ConfigFlags &= ~ImGuiConfigFlags_NavNoCaptureKeyboard;
12637 }
12638
12639 // Remap legacy clipboard handlers (OBSOLETED in 1.91.1, August 2024)
12640 if (g.IO.GetClipboardTextFn != NULL &&
12641 (g.PlatformIO.Platform_GetClipboardTextFn == NULL ||
12642 g.PlatformIO.Platform_GetClipboardTextFn == Platform_GetClipboardTextFn_DefaultImpl))
12643 g.PlatformIO.Platform_GetClipboardTextFn = [](ImGuiContext *ctx) {
12644 return ctx->IO.GetClipboardTextFn(ctx->IO.ClipboardUserData);
12645 };
12646 if (g.IO.SetClipboardTextFn != NULL &&
12647 (g.PlatformIO.Platform_SetClipboardTextFn == NULL ||
12648 g.PlatformIO.Platform_SetClipboardTextFn == Platform_SetClipboardTextFn_DefaultImpl))
12649 g.PlatformIO.Platform_SetClipboardTextFn = [](ImGuiContext *ctx, const char *text) {
12650 return ctx->IO.SetClipboardTextFn(ctx->IO.ClipboardUserData, text);
12651 };
12652#endif
12653
12654 // Perform simple check: error if Docking or Viewport are enabled _exactly_ on frame 1 (instead of frame 0 or
12655 // later), which is a common error leading to loss of .ini data.
12656 if (g.FrameCount == 1 && (g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable) &&
12657 (g.ConfigFlagsLastFrame & ImGuiConfigFlags_DockingEnable) == 0)
12658 IM_ASSERT(0 && "Please set DockingEnable before the first call to NewFrame()! Otherwise you will lose your "
12659 ".ini settings!");
12660 if (g.FrameCount == 1 && (g.IO.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) &&
12661 (g.ConfigFlagsLastFrame & ImGuiConfigFlags_ViewportsEnable) == 0)
12662 IM_ASSERT(0 && "Please set ViewportsEnable before the first call to NewFrame()! Otherwise you will lose your "
12663 ".ini settings!");
12664
12665 // Perform simple checks: multi-viewport and platform windows support
12666 if (g.IO.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
12667 {
12668 if ((g.IO.BackendFlags & ImGuiBackendFlags_PlatformHasViewports) &&
12669 (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasViewports))
12670 {
12671 IM_ASSERT((g.FrameCount == 0 || g.FrameCount == g.FrameCountPlatformEnded) &&
12672 "Forgot to call UpdatePlatformWindows() in main loop after EndFrame()? Check examples/ "
12673 "applications for reference.");
12674 IM_ASSERT(g.PlatformIO.Platform_CreateWindow != NULL && "Platform init didn't install handlers?");
12675 IM_ASSERT(g.PlatformIO.Platform_DestroyWindow != NULL && "Platform init didn't install handlers?");
12676 IM_ASSERT(g.PlatformIO.Platform_GetWindowPos != NULL && "Platform init didn't install handlers?");
12677 IM_ASSERT(g.PlatformIO.Platform_SetWindowPos != NULL && "Platform init didn't install handlers?");
12678 IM_ASSERT(g.PlatformIO.Platform_GetWindowSize != NULL && "Platform init didn't install handlers?");
12679 IM_ASSERT(g.PlatformIO.Platform_SetWindowSize != NULL && "Platform init didn't install handlers?");
12680 IM_ASSERT(g.PlatformIO.Monitors.Size > 0 && "Platform init didn't setup Monitors list?");
12681 IM_ASSERT((g.Viewports[0]->PlatformUserData != NULL || g.Viewports[0]->PlatformHandle != NULL) &&
12682 "Platform init didn't setup main viewport.");
12683 if (g.IO.ConfigDockingTransparentPayload && (g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable))
12684 IM_ASSERT(g.PlatformIO.Platform_SetWindowAlpha != NULL &&
12685 "Platform_SetWindowAlpha handler is required to use io.ConfigDockingTransparent!");
12686 }
12687 else
12688 {
12689 // Disable feature, our backends do not support it
12690 g.IO.ConfigFlags &= ~ImGuiConfigFlags_ViewportsEnable;
12691 }
12692
12693 // Perform simple checks on platform monitor data + compute a total bounding box for quick early outs
12694 for (ImGuiPlatformMonitor &mon : g.PlatformIO.Monitors)
12695 {
12696 IM_UNUSED(mon);
12697 IM_ASSERT(mon.MainSize.x > 0.0f && mon.MainSize.y > 0.0f && "Monitor main bounds not setup properly.");
12698 IM_ASSERT(ImRect(mon.MainPos, mon.MainPos + mon.MainSize)
12699 .Contains(ImRect(mon.WorkPos, mon.WorkPos + mon.WorkSize)) &&
12700 "Monitor work bounds not setup properly. If you don't have work area information, just copy "
12701 "MainPos/MainSize into them.");
12702 IM_ASSERT(mon.DpiScale > 0.0f && mon.DpiScale < 99.0f &&
12703 "Monitor DpiScale is invalid."); // Typical correct values would be between 1.0f and 4.0f
12704 }
12705 }
12706}
12707
12708static void ImGui::ErrorCheckEndFrameSanityChecks()
12709{
12710 // Verify that io.KeyXXX fields haven't been tampered with. Key mods should not be modified between NewFrame() and
12711 // EndFrame() One possible reason leading to this assert is that your backends update inputs _AFTER_ NewFrame(). It
12712 // is known that when some modal native windows called mid-frame takes focus away, some backends such as GLFW will
12713 // send key release events mid-frame. This would normally trigger this assertion and lead to sheared inputs.
12714 // We silently accommodate for this case by ignoring the case where all io.KeyXXX modifiers were released (aka
12715 // key_mod_flags == 0), while still correctly asserting on mid-frame key press events.
12716 ImGuiContext &g = *GImGui;
12717 const ImGuiKeyChord key_mods = GetMergedModsFromKeys();
12718 IM_UNUSED(g);
12719 IM_UNUSED(key_mods);
12720 IM_ASSERT((key_mods == 0 || g.IO.KeyMods == key_mods) &&
12721 "Mismatching io.KeyCtrl/io.KeyShift/io.KeyAlt/io.KeySuper vs io.KeyMods");
12722 IM_UNUSED(key_mods);
12723
12724 IM_ASSERT(g.CurrentWindowStack.Size == 1);
12725 IM_ASSERT(g.CurrentWindowStack[0].Window->IsFallbackWindow);
12726}
12727
12728// Save current stack sizes. Called e.g. by NewFrame() and by Begin() but may be called for manual recovery.
12729void ImGui::ErrorRecoveryStoreState(ImGuiErrorRecoveryState *state_out)
12730{
12731 ImGuiContext &g = *GImGui;
12732 state_out->SizeOfWindowStack = (short)g.CurrentWindowStack.Size;
12733 state_out->SizeOfIDStack = (short)g.CurrentWindow->IDStack.Size;
12734 state_out->SizeOfTreeStack =
12735 (short)g.CurrentWindow->DC.TreeDepth; // NOT g.TreeNodeStack.Size which is a partial stack!
12736 state_out->SizeOfColorStack = (short)g.ColorStack.Size;
12737 state_out->SizeOfStyleVarStack = (short)g.StyleVarStack.Size;
12738 state_out->SizeOfFontStack = (short)g.FontStack.Size;
12739 state_out->SizeOfFocusScopeStack = (short)g.FocusScopeStack.Size;
12740 state_out->SizeOfGroupStack = (short)g.GroupStack.Size;
12741 state_out->SizeOfItemFlagsStack = (short)g.ItemFlagsStack.Size;
12742 state_out->SizeOfBeginPopupStack = (short)g.BeginPopupStack.Size;
12743 state_out->SizeOfDisabledStack = (short)g.DisabledStackSize;
12744}
12745
12746// Chosen name "Try to recover" over e.g. "Restore" to suggest this is not a 100% guaranteed recovery.
12747// Called by e.g. EndFrame() but may be called for manual recovery.
12748// Attempt to recover full window stack.
12749void ImGui::ErrorRecoveryTryToRecoverState(const ImGuiErrorRecoveryState *state_in)
12750{
12751 // PVS-Studio V1044 is "Loop break conditions do not depend on the number of iterations"
12752 ImGuiContext &g = *GImGui;
12753 while (g.CurrentWindowStack.Size > state_in->SizeOfWindowStack) //-V1044
12754 {
12755 // Recap:
12756 // - Begin()/BeginChild() return false to indicate the window is collapsed or fully clipped.
12757 // - Always call a matching End() for each Begin() call, regardless of its return value!
12758 // - Begin/End and BeginChild/EndChild logic is KNOWN TO BE INCONSISTENT WITH ALL OTHER BEGIN/END FUNCTIONS.
12759 // - We will fix that in a future major update.
12760 ImGuiWindow *window = g.CurrentWindow;
12761 if (window->Flags & ImGuiWindowFlags_ChildWindow)
12762 {
12763 if (g.CurrentTable != NULL && g.CurrentTable->InnerWindow == g.CurrentWindow)
12764 {
12765 IM_ASSERT_USER_ERROR(0, "Missing EndTable()");
12766 EndTable();
12767 }
12768 else
12769 {
12770 IM_ASSERT_USER_ERROR(0, "Missing EndChild()");
12771 EndChild();
12772 }
12773 }
12774 else
12775 {
12776 IM_ASSERT_USER_ERROR(0, "Missing End()");
12777 End();
12778 }
12779 }
12780 if (g.CurrentWindowStack.Size == state_in->SizeOfWindowStack)
12781 ErrorRecoveryTryToRecoverWindowState(state_in);
12782}
12783
12784// Called by e.g. End() but may be called for manual recovery.
12785// Read '// Error Handling [BETA]' block in imgui_internal.h for details.
12786// Attempt to recover from incorrect usage of BeginXXX/EndXXX/PushXXX/PopXXX calls.
12787void ImGui::ErrorRecoveryTryToRecoverWindowState(const ImGuiErrorRecoveryState *state_in)
12788{
12789 ImGuiContext &g = *GImGui;
12790
12791 while (g.CurrentTable != NULL && g.CurrentTable->InnerWindow == g.CurrentWindow) //-V1044
12792 {
12793 IM_ASSERT_USER_ERROR(0, "Missing EndTable()");
12794 EndTable();
12795 }
12796
12797 ImGuiWindow *window = g.CurrentWindow;
12798
12799 // FIXME: Can't recover from inside BeginTabItem/EndTabItem yet.
12800 while (g.CurrentTabBar != NULL && g.CurrentTabBar->Window == window) //-V1044
12801 {
12802 IM_ASSERT_USER_ERROR(0, "Missing EndTabBar()");
12803 EndTabBar();
12804 }
12805 while (g.CurrentMultiSelect != NULL && g.CurrentMultiSelect->Storage->Window == window) //-V1044
12806 {
12807 IM_ASSERT_USER_ERROR(0, "Missing EndMultiSelect()");
12808 EndMultiSelect();
12809 }
12810 if (window->DC.MenuBarAppending) //-V1044
12811 {
12812 IM_ASSERT_USER_ERROR(0, "Missing EndMenuBar()");
12813 EndMenuBar();
12814 }
12815 while (window->DC.TreeDepth > state_in->SizeOfTreeStack) //-V1044
12816 {
12817 IM_ASSERT_USER_ERROR(0, "Missing TreePop()");
12818 TreePop();
12819 }
12820 while (g.GroupStack.Size > state_in->SizeOfGroupStack) //-V1044
12821 {
12822 IM_ASSERT_USER_ERROR(0, "Missing EndGroup()");
12823 EndGroup();
12824 }
12825 IM_ASSERT(g.GroupStack.Size == state_in->SizeOfGroupStack);
12826 while (window->IDStack.Size > state_in->SizeOfIDStack) //-V1044
12827 {
12828 IM_ASSERT_USER_ERROR(0, "Missing PopID()");
12829 PopID();
12830 }
12831 while (g.DisabledStackSize > state_in->SizeOfDisabledStack) //-V1044
12832 {
12833 IM_ASSERT_USER_ERROR(0, "Missing EndDisabled()");
12834 if (g.CurrentItemFlags & ImGuiItemFlags_Disabled)
12835 EndDisabled();
12836 else
12837 {
12838 EndDisabledOverrideReenable();
12839 g.CurrentWindowStack.back().DisabledOverrideReenable = false;
12840 }
12841 }
12842 IM_ASSERT(g.DisabledStackSize == state_in->SizeOfDisabledStack);
12843 while (g.ColorStack.Size > state_in->SizeOfColorStack) //-V1044
12844 {
12845 IM_ASSERT_USER_ERROR(0, "Missing PopStyleColor()");
12846 PopStyleColor();
12847 }
12848 while (g.ItemFlagsStack.Size > state_in->SizeOfItemFlagsStack) //-V1044
12849 {
12850 IM_ASSERT_USER_ERROR(0, "Missing PopItemFlag()");
12851 PopItemFlag();
12852 }
12853 while (g.StyleVarStack.Size > state_in->SizeOfStyleVarStack) //-V1044
12854 {
12855 IM_ASSERT_USER_ERROR(0, "Missing PopStyleVar()");
12856 PopStyleVar();
12857 }
12858 while (g.FontStack.Size > state_in->SizeOfFontStack) //-V1044
12859 {
12860 IM_ASSERT_USER_ERROR(0, "Missing PopFont()");
12861 PopFont();
12862 }
12863 while (g.FocusScopeStack.Size > state_in->SizeOfFocusScopeStack) //-V1044
12864 {
12865 IM_ASSERT_USER_ERROR(0, "Missing PopFocusScope()");
12866 PopFocusScope();
12867 }
12868 // IM_ASSERT(g.FocusScopeStack.Size == state_in->SizeOfFocusScopeStack);
12869}
12870
12871bool ImGui::ErrorLog(const char *msg)
12872{
12873 ImGuiContext &g = *GImGui;
12874
12875 // Output to debug log
12876#ifndef IMGUI_DISABLE_DEBUG_TOOLS
12877 ImGuiWindow *window = g.CurrentWindow;
12878
12879 if (g.IO.ConfigErrorRecoveryEnableDebugLog)
12880 {
12881 if (g.ErrorFirst)
12882 IMGUI_DEBUG_LOG_ERROR("[imgui-error] (current settings: Assert=%d, Log=%d, Tooltip=%d)\n",
12883 g.IO.ConfigErrorRecoveryEnableAssert, g.IO.ConfigErrorRecoveryEnableDebugLog,
12884 g.IO.ConfigErrorRecoveryEnableTooltip);
12885 IMGUI_DEBUG_LOG_ERROR("[imgui-error] In window '%s': %s\n", window ? window->Name : "NULL", msg);
12886 }
12887 g.ErrorFirst = false;
12888
12889 // Output to tooltip
12890 if (g.IO.ConfigErrorRecoveryEnableTooltip)
12891 {
12892 if (g.WithinFrameScope && BeginErrorTooltip())
12893 {
12894 if (g.ErrorCountCurrentFrame < 20)
12895 {
12896 Text("In window '%s': %s", window ? window->Name : "NULL", msg);
12897 if (window && (!window->IsFallbackWindow || window->WasActive))
12898 GetForegroundDrawList(window)->AddRect(window->Pos, window->Pos + window->Size,
12899 IM_COL32(255, 0, 0, 255));
12900 }
12901 if (g.ErrorCountCurrentFrame == 20)
12902 Text("(and more errors)");
12903 // EndFrame() will amend debug buttons to this window, after all errors have been submitted.
12904 EndErrorTooltip();
12905 }
12906 g.ErrorCountCurrentFrame++;
12907 }
12908#endif
12909
12910 // Output to callback
12911 if (g.ErrorCallback != NULL)
12912 g.ErrorCallback(&g, g.ErrorCallbackUserData, msg);
12913
12914 // Return whether we should assert
12915 return g.IO.ConfigErrorRecoveryEnableAssert;
12916}
12917
12918void ImGui::ErrorCheckEndFrameFinalizeErrorTooltip()
12919{
12920#ifndef IMGUI_DISABLE_DEBUG_TOOLS
12921 ImGuiContext &g = *GImGui;
12922 if (g.DebugDrawIdConflicts != 0 && g.IO.KeyCtrl == false)
12923 g.DebugDrawIdConflictsCount = g.HoveredIdPreviousFrameItemCount;
12924 if (g.DebugDrawIdConflicts != 0 && g.DebugItemPickerActive == false && BeginErrorTooltip())
12925 {
12926 Text("Programmer error: %d visible items with conflicting ID!", g.DebugDrawIdConflictsCount);
12927 BulletText("Code should use PushID()/PopID() in loops, or append \"##xx\" to same-label identifiers!");
12928 BulletText("Empty label e.g. Button(\"\") == same ID as parent widget/node. Use Button(\"##xx\") instead!");
12929 // BulletText("Code intending to use duplicate ID may use e.g. PushItemFlag(ImGuiItemFlags_AllowDuplicateId,
12930 // true); ... PopItemFlag()"); // Not making this too visible for fear of it being abused.
12931 BulletText("Set io.ConfigDebugHighlightIdConflicts=false to disable this warning in non-programmers builds.");
12932 Separator();
12933 if (g.IO.ConfigDebugHighlightIdConflictsShowItemPicker)
12934 {
12935 Text("(Hold CTRL to: use ");
12936 SameLine(0.0f, 0.0f);
12937 if (SmallButton("Item Picker"))
12938 DebugStartItemPicker();
12939 SameLine(0.0f, 0.0f);
12940 Text(" to break in item call-stack, or ");
12941 }
12942 else
12943 {
12944 Text("(Hold CTRL to ");
12945 }
12946 SameLine(0.0f, 0.0f);
12947 if (SmallButton("Open FAQ->About ID Stack System") && g.PlatformIO.Platform_OpenInShellFn != NULL)
12948 g.PlatformIO.Platform_OpenInShellFn(&g,
12949 "https://github.com/ocornut/imgui/blob/master/docs/FAQ.md#qa-usage");
12950 SameLine(0.0f, 0.0f);
12951 Text(")");
12952 EndErrorTooltip();
12953 }
12954
12955 if (g.ErrorCountCurrentFrame > 0 && BeginErrorTooltip()) // Amend at end of frame
12956 {
12957 Separator();
12958 Text("(Hold CTRL to:");
12959 SameLine();
12960 if (SmallButton("Enable Asserts"))
12961 g.IO.ConfigErrorRecoveryEnableAssert = true;
12962 // SameLine();
12963 // if (SmallButton("Hide Error Tooltips"))
12964 // g.IO.ConfigErrorRecoveryEnableTooltip = false; // Too dangerous
12965 SameLine(0, 0);
12966 Text(")");
12967 EndErrorTooltip();
12968 }
12969#endif
12970}
12971
12972// Pseudo-tooltip. Follow mouse until CTRL is held. When CTRL is held we lock position, allowing to click it.
12973bool ImGui::BeginErrorTooltip()
12974{
12975 ImGuiContext &g = *GImGui;
12976 ImGuiWindow *window = FindWindowByName("##Tooltip_Error");
12977 const bool use_locked_pos = (g.IO.KeyCtrl && window && window->WasActive);
12978 PushStyleColor(ImGuiCol_PopupBg, ImLerp(g.Style.Colors[ImGuiCol_PopupBg], ImVec4(1.0f, 0.0f, 0.0f, 1.0f), 0.15f));
12979 if (use_locked_pos)
12980 SetNextWindowPos(g.ErrorTooltipLockedPos);
12981 bool is_visible =
12982 Begin("##Tooltip_Error", NULL,
12983 ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |
12984 ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize);
12985 PopStyleColor();
12986 if (is_visible && g.CurrentWindow->BeginCount == 1)
12987 {
12988 SeparatorText("MESSAGE FROM DEAR IMGUI");
12989 BringWindowToDisplayFront(g.CurrentWindow);
12990 BringWindowToFocusFront(g.CurrentWindow);
12991 g.ErrorTooltipLockedPos = GetWindowPos();
12992 }
12993 else if (!is_visible)
12994 {
12995 End();
12996 }
12997 return is_visible;
12998}
12999
13000void ImGui::EndErrorTooltip()
13001{
13002 End();
13003}
13004
13005//-----------------------------------------------------------------------------
13006// [SECTION] ITEM SUBMISSION
13007//-----------------------------------------------------------------------------
13008// - KeepAliveID()
13009// - ItemAdd()
13010//-----------------------------------------------------------------------------
13011
13012// Code not using ItemAdd() may need to call this manually otherwise ActiveId will be cleared. In IMGUI_VERSION_NUM <
13013// 18717 this was called by GetID().
13014void ImGui::KeepAliveID(ImGuiID id)
13015{
13016 ImGuiContext &g = *GImGui;
13017 if (g.ActiveId == id)
13018 g.ActiveIdIsAlive = id;
13019 if (g.DeactivatedItemData.ID == id)
13020 g.DeactivatedItemData.IsAlive = true;
13021}
13022
13023// Declare item bounding box for clipping and interaction.
13024// Note that the size can be different than the one provided to ItemSize(). Typically, widgets that spread over
13025// available surface declare their minimum size requirement to ItemSize() and provide a larger region to ItemAdd() which
13026// is used drawing/interaction. THIS IS IN THE PERFORMANCE CRITICAL PATH (UNTIL THE CLIPPING TEST AND EARLY-RETURN)
13027IM_MSVC_RUNTIME_CHECKS_OFF
13028bool ImGui::ItemAdd(const ImRect &bb, ImGuiID id, const ImRect *nav_bb_arg, ImGuiItemFlags extra_flags)
13029{
13030 ImGuiContext &g = *GImGui;
13031 ImGuiWindow *window = g.CurrentWindow;
13032
13033 // Set item data
13034 // (DisplayRect is left untouched, made valid when ImGuiItemStatusFlags_HasDisplayRect is set)
13035 g.LastItemData.ID = id;
13036 g.LastItemData.Rect = bb;
13037 g.LastItemData.NavRect = nav_bb_arg ? *nav_bb_arg : bb;
13038 g.LastItemData.ItemFlags = g.CurrentItemFlags | g.NextItemData.ItemFlags | extra_flags;
13039 g.LastItemData.StatusFlags = ImGuiItemStatusFlags_None;
13040 // Note: we don't copy 'g.NextItemData.SelectionUserData' to an hypothetical g.LastItemData.SelectionUserData: since
13041 // the former is not cleared.
13042
13043 if (id != 0)
13044 {
13045 KeepAliveID(id);
13046
13047 // Directional navigation processing
13048 // Runs prior to clipping early-out
13049 // (a) So that NavInitRequest can be honored, for newly opened windows to select a default widget
13050 // (b) So that we can scroll up/down past clipped items. This adds a small O(N) cost to regular navigation
13051 // requests
13052 // unfortunately, but it is still limited to one window. It may not scale very well for windows with ten of
13053 // thousands of item, but at least NavMoveRequest is only set on user interaction, aka maximum once a
13054 // frame. We could early out with "if (is_clipped && !g.NavInitRequest) return false;" but when we wouldn't
13055 // be able to reach unclipped widgets. This would work if user had explicit scrolling control (e.g. mapped
13056 // on a stick).
13057 // We intentionally don't check if g.NavWindow != NULL because g.NavAnyRequest should only be set when it is non
13058 // null. If we crash on a NULL g.NavWindow we need to fix the bug elsewhere.
13059 if (!(g.LastItemData.ItemFlags & ImGuiItemFlags_NoNav))
13060 {
13061 // FIMXE-NAV: investigate changing the window tests into a simple 'if (g.NavFocusScopeId ==
13062 // g.CurrentFocusScopeId)' test.
13063 window->DC.NavLayersActiveMaskNext |= (1 << window->DC.NavLayerCurrent);
13064 if (g.NavId == id || g.NavAnyRequest)
13065 if (g.NavWindow->RootWindowForNav == window->RootWindowForNav)
13066 if (window == g.NavWindow ||
13067 ((window->ChildFlags | g.NavWindow->ChildFlags) & ImGuiChildFlags_NavFlattened))
13068 NavProcessItem();
13069 }
13070
13071 if (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasShortcut)
13072 ItemHandleShortcut(id);
13073 }
13074
13075 // Lightweight clear of SetNextItemXXX data.
13076 g.NextItemData.HasFlags = ImGuiNextItemDataFlags_None;
13077 g.NextItemData.ItemFlags = ImGuiItemFlags_None;
13078
13079#ifdef IMGUI_ENABLE_TEST_ENGINE
13080 if (id != 0)
13081 IMGUI_TEST_ENGINE_ITEM_ADD(id, g.LastItemData.NavRect, &g.LastItemData);
13082#endif
13083
13084 // Clipping test
13085 // (this is an inline copy of IsClippedEx() so we can reuse the is_rect_visible value, otherwise we'd do 'if
13086 // (IsClippedEx(bb, id)) return false') g.NavActivateId is not necessarily == g.NavId, in the case of remote
13087 // activation (e.g. shortcuts)
13088 const bool is_rect_visible = bb.Overlaps(window->ClipRect);
13089 if (!is_rect_visible)
13090 if (id == 0 || (id != g.ActiveId && id != g.ActiveIdPreviousFrame && id != g.NavId && id != g.NavActivateId))
13091 if (!g.ItemUnclipByLog)
13092 return false;
13093
13094 // [DEBUG]
13095#ifndef IMGUI_DISABLE_DEBUG_TOOLS
13096 if (id != 0)
13097 {
13098 if (id == g.DebugLocateId)
13099 DebugLocateItemResolveWithLastItem();
13100
13101 // [DEBUG] People keep stumbling on this problem and using "" as identifier in the root of a window instead of
13102 // "##something". Empty identifier are valid and useful in a small amount of cases, but 99.9% of the time you
13103 // want to use "##something". READ THE FAQ: https://dearimgui.com/faq
13104 IM_ASSERT(id != window->ID && "Cannot have an empty ID at the root of a window. If you need an empty label, "
13105 "use ## and read the FAQ about how the ID Stack works!");
13106 }
13107 // if (g.IO.KeyAlt) window->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(255,255,0,120)); // [DEBUG]
13108 // if ((g.LastItemData.ItemFlags & ImGuiItemFlags_NoNav) == 0)
13109 // window->DrawList->AddRect(g.LastItemData.NavRect.Min, g.LastItemData.NavRect.Max, IM_COL32(255,255,0,255));
13110 // // [DEBUG]
13111#endif
13112
13113 if (id != 0 && g.DeactivatedItemData.ID == id)
13114 g.DeactivatedItemData.ElapseFrame = g.FrameCount;
13115
13116 // We need to calculate this now to take account of the current clipping rectangle (as items like Selectable may
13117 // change them)
13118 if (is_rect_visible)
13119 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Visible;
13120 if (IsMouseHoveringRect(bb.Min, bb.Max))
13121 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredRect;
13122 return true;
13123}
13124IM_MSVC_RUNTIME_CHECKS_RESTORE
13125
13126//-----------------------------------------------------------------------------
13127// [SECTION] LAYOUT
13128//-----------------------------------------------------------------------------
13129// - ItemSize()
13130// - SameLine()
13131// - GetCursorScreenPos()
13132// - SetCursorScreenPos()
13133// - GetCursorPos(), GetCursorPosX(), GetCursorPosY()
13134// - SetCursorPos(), SetCursorPosX(), SetCursorPosY()
13135// - GetCursorStartPos()
13136// - Indent()
13137// - Unindent()
13138// - SetNextItemWidth()
13139// - PushItemWidth()
13140// - PushMultiItemsWidths()
13141// - PopItemWidth()
13142// - CalcItemWidth()
13143// - CalcItemSize()
13144// - GetTextLineHeight()
13145// - GetTextLineHeightWithSpacing()
13146// - GetFrameHeight()
13147// - GetFrameHeightWithSpacing()
13148// - GetContentRegionMax()
13149// - GetContentRegionAvail(),
13150// - BeginGroup()
13151// - EndGroup()
13152// Also see in imgui_widgets: tab bars, and in imgui_tables: tables, columns.
13153//-----------------------------------------------------------------------------
13154
13155// Advance cursor given item size for layout.
13156// Register minimum needed size so it can extend the bounding box used for auto-fit calculation.
13157// See comments in ItemAdd() about how/why the size provided to ItemSize() vs ItemAdd() may often different.
13158// THIS IS IN THE PERFORMANCE CRITICAL PATH.
13159IM_MSVC_RUNTIME_CHECKS_OFF
13160void ImGui::ItemSize(const ImVec2 &size, float text_baseline_y)
13161{
13162 ImGuiContext &g = *GImGui;
13163 ImGuiWindow *window = g.CurrentWindow;
13164 if (window->SkipItems)
13165 return;
13166
13167 // We increase the height in this function to accommodate for baseline offset.
13168 // In theory we should be offsetting the starting position (window->DC.CursorPos), that will be the topic of a
13169 // larger refactor, but since ItemSize() is not yet an API that moves the cursor (to handle e.g. wrapping) enlarging
13170 // the height has the same effect.
13171 const float offset_to_match_baseline_y =
13172 (text_baseline_y >= 0) ? ImMax(0.0f, window->DC.CurrLineTextBaseOffset - text_baseline_y) : 0.0f;
13173
13174 const float line_y1 = window->DC.IsSameLine ? window->DC.CursorPosPrevLine.y : window->DC.CursorPos.y;
13175 const float line_height = ImMax(window->DC.CurrLineSize.y, /*ImMax(*/ window->DC.CursorPos.y - line_y1 /*, 0.0f)*/ +
13176 size.y + offset_to_match_baseline_y);
13177
13178 // Always align ourselves on pixel boundaries
13179 // if (g.IO.KeyAlt) window->DrawList->AddRect(window->DC.CursorPos, window->DC.CursorPos + ImVec2(size.x,
13180 // line_height), IM_COL32(255,0,0,200)); // [DEBUG]
13181 window->DC.CursorPosPrevLine.x = window->DC.CursorPos.x + size.x;
13182 window->DC.CursorPosPrevLine.y = line_y1;
13183 window->DC.CursorPos.x = IM_TRUNC(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); // Next line
13184 window->DC.CursorPos.y = IM_TRUNC(line_y1 + line_height + g.Style.ItemSpacing.y); // Next line
13185 window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPosPrevLine.x);
13186 window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y - g.Style.ItemSpacing.y);
13187 // if (g.IO.KeyAlt) window->DrawList->AddCircle(window->DC.CursorMaxPos, 3.0f, IM_COL32(255,0,0,255), 4); // [DEBUG]
13188
13189 window->DC.PrevLineSize.y = line_height;
13190 window->DC.CurrLineSize.y = 0.0f;
13191 window->DC.PrevLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, text_baseline_y);
13192 window->DC.CurrLineTextBaseOffset = 0.0f;
13193 window->DC.IsSameLine = window->DC.IsSetPos = false;
13194
13195 // Horizontal layout mode
13196 if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
13197 SameLine();
13198}
13199IM_MSVC_RUNTIME_CHECKS_RESTORE
13200
13201// Gets back to previous line and continue with horizontal layout
13202// offset_from_start_x == 0 : follow right after previous item
13203// offset_from_start_x != 0 : align to specified x position (relative to window/group left)
13204// spacing_w < 0 : use default spacing if offset_from_start_x == 0, no spacing if offset_from_start_x !=
13205// 0 spacing_w >= 0 : enforce spacing amount
13206void ImGui::SameLine(float offset_from_start_x, float spacing_w)
13207{
13208 ImGuiContext &g = *GImGui;
13209 ImGuiWindow *window = g.CurrentWindow;
13210 if (window->SkipItems)
13211 return;
13212
13213 if (offset_from_start_x != 0.0f)
13214 {
13215 if (spacing_w < 0.0f)
13216 spacing_w = 0.0f;
13217 window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + offset_from_start_x + spacing_w +
13218 window->DC.GroupOffset.x + window->DC.ColumnsOffset.x;
13219 window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y;
13220 }
13221 else
13222 {
13223 if (spacing_w < 0.0f)
13224 spacing_w = g.Style.ItemSpacing.x;
13225 window->DC.CursorPos.x = window->DC.CursorPosPrevLine.x + spacing_w;
13226 window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y;
13227 }
13228 window->DC.CurrLineSize = window->DC.PrevLineSize;
13229 window->DC.CurrLineTextBaseOffset = window->DC.PrevLineTextBaseOffset;
13230 window->DC.IsSameLine = true;
13231}
13232
13233ImVec2 ImGui::GetCursorScreenPos()
13234{
13235 ImGuiWindow *window = GetCurrentWindowRead();
13236 return window->DC.CursorPos;
13237}
13238
13239void ImGui::SetCursorScreenPos(const ImVec2 &pos)
13240{
13241 ImGuiWindow *window = GetCurrentWindow();
13242 window->DC.CursorPos = pos;
13243 // window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos);
13244 window->DC.IsSetPos = true;
13245}
13246
13247// User generally sees positions in window coordinates. Internally we store CursorPos in absolute screen coordinates
13248// because it is more convenient. Conversion happens as we pass the value to user, but it makes our naming convention
13249// confusing because GetCursorPos() == (DC.CursorPos - window.Pos). May want to rename 'DC.CursorPos'.
13250ImVec2 ImGui::GetCursorPos()
13251{
13252 ImGuiWindow *window = GetCurrentWindowRead();
13253 return window->DC.CursorPos - window->Pos + window->Scroll;
13254}
13255
13256float ImGui::GetCursorPosX()
13257{
13258 ImGuiWindow *window = GetCurrentWindowRead();
13259 return window->DC.CursorPos.x - window->Pos.x + window->Scroll.x;
13260}
13261
13262float ImGui::GetCursorPosY()
13263{
13264 ImGuiWindow *window = GetCurrentWindowRead();
13265 return window->DC.CursorPos.y - window->Pos.y + window->Scroll.y;
13266}
13267
13268void ImGui::SetCursorPos(const ImVec2 &local_pos)
13269{
13270 ImGuiWindow *window = GetCurrentWindow();
13271 window->DC.CursorPos = window->Pos - window->Scroll + local_pos;
13272 // window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos);
13273 window->DC.IsSetPos = true;
13274}
13275
13276void ImGui::SetCursorPosX(float x)
13277{
13278 ImGuiWindow *window = GetCurrentWindow();
13279 window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + x;
13280 // window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPos.x);
13281 window->DC.IsSetPos = true;
13282}
13283
13284void ImGui::SetCursorPosY(float y)
13285{
13286 ImGuiWindow *window = GetCurrentWindow();
13287 window->DC.CursorPos.y = window->Pos.y - window->Scroll.y + y;
13288 // window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y);
13289 window->DC.IsSetPos = true;
13290}
13291
13292ImVec2 ImGui::GetCursorStartPos()
13293{
13294 ImGuiWindow *window = GetCurrentWindowRead();
13295 return window->DC.CursorStartPos - window->Pos;
13296}
13297
13298void ImGui::Indent(float indent_w)
13299{
13300 ImGuiContext &g = *GImGui;
13301 ImGuiWindow *window = GetCurrentWindow();
13302 window->DC.Indent.x += (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing;
13303 window->DC.CursorPos.x = window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x;
13304}
13305
13306void ImGui::Unindent(float indent_w)
13307{
13308 ImGuiContext &g = *GImGui;
13309 ImGuiWindow *window = GetCurrentWindow();
13310 window->DC.Indent.x -= (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing;
13311 window->DC.CursorPos.x = window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x;
13312}
13313
13314// Affect large frame+labels widgets only.
13315void ImGui::SetNextItemWidth(float item_width)
13316{
13317 ImGuiContext &g = *GImGui;
13318 g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasWidth;
13319 g.NextItemData.Width = item_width;
13320}
13321
13322// FIXME: Remove the == 0.0f behavior?
13323void ImGui::PushItemWidth(float item_width)
13324{
13325 ImGuiContext &g = *GImGui;
13326 ImGuiWindow *window = g.CurrentWindow;
13327 window->DC.ItemWidthStack.push_back(window->DC.ItemWidth); // Backup current width
13328 window->DC.ItemWidth = (item_width == 0.0f ? window->ItemWidthDefault : item_width);
13329 g.NextItemData.HasFlags &= ~ImGuiNextItemDataFlags_HasWidth;
13330}
13331
13332void ImGui::PushMultiItemsWidths(int components, float w_full)
13333{
13334 ImGuiContext &g = *GImGui;
13335 ImGuiWindow *window = g.CurrentWindow;
13336 IM_ASSERT(components > 0);
13337 const ImGuiStyle &style = g.Style;
13338 window->DC.ItemWidthStack.push_back(window->DC.ItemWidth); // Backup current width
13339 float w_items = w_full - style.ItemInnerSpacing.x * (components - 1);
13340 float prev_split = w_items;
13341 for (int i = components - 1; i > 0; i--)
13342 {
13343 float next_split = IM_TRUNC(w_items * i / components);
13344 window->DC.ItemWidthStack.push_back(ImMax(prev_split - next_split, 1.0f));
13345 prev_split = next_split;
13346 }
13347 window->DC.ItemWidth = ImMax(prev_split, 1.0f);
13348 g.NextItemData.HasFlags &= ~ImGuiNextItemDataFlags_HasWidth;
13349}
13350
13351void ImGui::PopItemWidth()
13352{
13353 ImGuiContext &g = *GImGui;
13354 ImGuiWindow *window = g.CurrentWindow;
13355 if (window->DC.ItemWidthStack.Size <= 0)
13356 {
13357 IM_ASSERT_USER_ERROR(0, "Calling PopItemWidth() too many times!");
13358 return;
13359 }
13360 window->DC.ItemWidth = window->DC.ItemWidthStack.back();
13361 window->DC.ItemWidthStack.pop_back();
13362}
13363
13364// Calculate default item width given value passed to PushItemWidth() or SetNextItemWidth().
13365// The SetNextItemWidth() data is generally cleared/consumed by ItemAdd() or NextItemData.ClearFlags()
13366float ImGui::CalcItemWidth()
13367{
13368 ImGuiContext &g = *GImGui;
13369 ImGuiWindow *window = g.CurrentWindow;
13370 float w;
13371 if (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasWidth)
13372 w = g.NextItemData.Width;
13373 else
13374 w = window->DC.ItemWidth;
13375 if (w < 0.0f)
13376 {
13377 float region_avail_x = GetContentRegionAvail().x;
13378 w = ImMax(1.0f, region_avail_x + w);
13379 }
13380 w = IM_TRUNC(w);
13381 return w;
13382}
13383
13384// [Internal] Calculate full item size given user provided 'size' parameter and default width/height. Default width is
13385// often == CalcItemWidth(). Those two functions CalcItemWidth vs CalcItemSize are awkwardly named because they are not
13386// fully symmetrical. Note that only CalcItemWidth() is publicly exposed. The 4.0f here may be changed to match
13387// CalcItemWidth() and/or BeginChild() (right now we have a mismatch which is harmless but undesirable)
13388ImVec2 ImGui::CalcItemSize(ImVec2 size, float default_w, float default_h)
13389{
13390 ImVec2 avail;
13391 if (size.x < 0.0f || size.y < 0.0f)
13392 avail = GetContentRegionAvail();
13393
13394 if (size.x == 0.0f)
13395 size.x = default_w;
13396 else if (size.x < 0.0f)
13397 size.x = ImMax(4.0f, avail.x + size.x); // <-- size.x is negative here so we are subtracting
13398
13399 if (size.y == 0.0f)
13400 size.y = default_h;
13401 else if (size.y < 0.0f)
13402 size.y = ImMax(4.0f, avail.y + size.y); // <-- size.y is negative here so we are subtracting
13403
13404 return size;
13405}
13406
13407float ImGui::GetTextLineHeight()
13408{
13409 ImGuiContext &g = *GImGui;
13410 return g.FontSize;
13411}
13412
13413float ImGui::GetTextLineHeightWithSpacing()
13414{
13415 ImGuiContext &g = *GImGui;
13416 return g.FontSize + g.Style.ItemSpacing.y;
13417}
13418
13419float ImGui::GetFrameHeight()
13420{
13421 ImGuiContext &g = *GImGui;
13422 return g.FontSize + g.Style.FramePadding.y * 2.0f;
13423}
13424
13425float ImGui::GetFrameHeightWithSpacing()
13426{
13427 ImGuiContext &g = *GImGui;
13428 return g.FontSize + g.Style.FramePadding.y * 2.0f + g.Style.ItemSpacing.y;
13429}
13430
13431ImVec2 ImGui::GetContentRegionAvail()
13432{
13433 ImGuiContext &g = *GImGui;
13434 ImGuiWindow *window = g.CurrentWindow;
13435 ImVec2 mx = (window->DC.CurrentColumns || g.CurrentTable) ? window->WorkRect.Max : window->ContentRegionRect.Max;
13436 return mx - window->DC.CursorPos;
13437}
13438
13439#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
13440
13441// You should never need those functions. Always use GetCursorScreenPos() and GetContentRegionAvail()!
13442// They are bizarre local-coordinates which don't play well with scrolling.
13443ImVec2 ImGui::GetContentRegionMax()
13444{
13445 return GetContentRegionAvail() + GetCursorScreenPos() - GetWindowPos();
13446}
13447
13448ImVec2 ImGui::GetWindowContentRegionMin()
13449{
13450 ImGuiWindow *window = GImGui->CurrentWindow;
13451 return window->ContentRegionRect.Min - window->Pos;
13452}
13453
13454ImVec2 ImGui::GetWindowContentRegionMax()
13455{
13456 ImGuiWindow *window = GImGui->CurrentWindow;
13457 return window->ContentRegionRect.Max - window->Pos;
13458}
13459#endif
13460
13461// Lock horizontal starting position + capture group bounding box into one "item" (so you can use IsItemHovered() or
13462// layout primitives such as SameLine() on whole group, etc.) Groups are currently a mishmash of functionalities which
13463// should perhaps be clarified and separated.
13464// FIXME-OPT: Could we safely early out on ->SkipItems?
13465void ImGui::BeginGroup()
13466{
13467 ImGuiContext &g = *GImGui;
13468 ImGuiWindow *window = g.CurrentWindow;
13469
13470 g.GroupStack.resize(g.GroupStack.Size + 1);
13471 ImGuiGroupData &group_data = g.GroupStack.back();
13472 group_data.WindowID = window->ID;
13473 group_data.BackupCursorPos = window->DC.CursorPos;
13474 group_data.BackupCursorPosPrevLine = window->DC.CursorPosPrevLine;
13475 group_data.BackupCursorMaxPos = window->DC.CursorMaxPos;
13476 group_data.BackupIndent = window->DC.Indent;
13477 group_data.BackupGroupOffset = window->DC.GroupOffset;
13478 group_data.BackupCurrLineSize = window->DC.CurrLineSize;
13479 group_data.BackupCurrLineTextBaseOffset = window->DC.CurrLineTextBaseOffset;
13480 group_data.BackupActiveIdIsAlive = g.ActiveIdIsAlive;
13481 group_data.BackupHoveredIdIsAlive = g.HoveredId != 0;
13482 group_data.BackupIsSameLine = window->DC.IsSameLine;
13483 group_data.BackupDeactivatedIdIsAlive = g.DeactivatedItemData.IsAlive;
13484 group_data.EmitItem = true;
13485
13486 window->DC.GroupOffset.x = window->DC.CursorPos.x - window->Pos.x - window->DC.ColumnsOffset.x;
13487 window->DC.Indent = window->DC.GroupOffset;
13488 window->DC.CursorMaxPos = window->DC.CursorPos;
13489 window->DC.CurrLineSize = ImVec2(0.0f, 0.0f);
13490 if (g.LogEnabled)
13491 g.LogLinePosY = -FLT_MAX; // To enforce a carriage return
13492}
13493
13494void ImGui::EndGroup()
13495{
13496 ImGuiContext &g = *GImGui;
13497 ImGuiWindow *window = g.CurrentWindow;
13498 IM_ASSERT(g.GroupStack.Size > 0); // Mismatched BeginGroup()/EndGroup() calls
13499
13500 ImGuiGroupData &group_data = g.GroupStack.back();
13501 IM_ASSERT(group_data.WindowID == window->ID); // EndGroup() in wrong window?
13502
13503 if (window->DC.IsSetPos)
13504 ErrorCheckUsingSetCursorPosToExtendParentBoundaries();
13505
13506 // Include LastItemData.Rect.Max as a workaround for e.g. EndTable() undershooting with CursorMaxPos report. (#7543)
13507 ImRect group_bb(group_data.BackupCursorPos,
13508 ImMax(ImMax(window->DC.CursorMaxPos, g.LastItemData.Rect.Max), group_data.BackupCursorPos));
13509 window->DC.CursorPos = group_data.BackupCursorPos;
13510 window->DC.CursorPosPrevLine = group_data.BackupCursorPosPrevLine;
13511 window->DC.CursorMaxPos = ImMax(group_data.BackupCursorMaxPos, group_bb.Max);
13512 window->DC.Indent = group_data.BackupIndent;
13513 window->DC.GroupOffset = group_data.BackupGroupOffset;
13514 window->DC.CurrLineSize = group_data.BackupCurrLineSize;
13515 window->DC.CurrLineTextBaseOffset = group_data.BackupCurrLineTextBaseOffset;
13516 window->DC.IsSameLine = group_data.BackupIsSameLine;
13517 if (g.LogEnabled)
13518 g.LogLinePosY = -FLT_MAX; // To enforce a carriage return
13519
13520 if (!group_data.EmitItem)
13521 {
13522 g.GroupStack.pop_back();
13523 return;
13524 }
13525
13526 window->DC.CurrLineTextBaseOffset =
13527 ImMax(window->DC.PrevLineTextBaseOffset,
13528 group_data.BackupCurrLineTextBaseOffset); // FIXME: Incorrect, we should grab the base offset from the
13529 // *first line* of the group but it is hard to obtain now.
13530 ItemSize(group_bb.GetSize());
13531 ItemAdd(group_bb, 0, NULL, ImGuiItemFlags_NoTabStop);
13532
13533 // If the current ActiveId was declared within the boundary of our group, we copy it to LastItemId so
13534 // IsItemActive(), IsItemDeactivated() etc. will be functional on the entire group. It would be neater if we
13535 // replaced window.DC.LastItemId by e.g. 'bool LastItemIsActive', but would put a little more burden on individual
13536 // widgets. Also if you grep for LastItemId you'll notice it is only used in that context. (The two tests not the
13537 // same because ActiveIdIsAlive is an ID itself, in order to be able to handle ActiveId being overwritten during the
13538 // frame.)
13539 const bool group_contains_curr_active_id =
13540 (group_data.BackupActiveIdIsAlive != g.ActiveId) && (g.ActiveIdIsAlive == g.ActiveId) && g.ActiveId;
13541 const bool group_contains_deactivated_id =
13542 (group_data.BackupDeactivatedIdIsAlive == false) && (g.DeactivatedItemData.IsAlive == true);
13543 if (group_contains_curr_active_id)
13544 g.LastItemData.ID = g.ActiveId;
13545 else if (group_contains_deactivated_id)
13546 g.LastItemData.ID = g.DeactivatedItemData.ID;
13547 g.LastItemData.Rect = group_bb;
13548
13549 // Forward Hovered flag
13550 const bool group_contains_curr_hovered_id = (group_data.BackupHoveredIdIsAlive == false) && g.HoveredId != 0;
13551 if (group_contains_curr_hovered_id)
13552 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow;
13553
13554 // Forward Edited flag
13555 if (group_contains_curr_active_id && g.ActiveIdHasBeenEditedThisFrame)
13556 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Edited;
13557
13558 // Forward Deactivated flag
13559 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDeactivated;
13560 if (group_contains_deactivated_id)
13561 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Deactivated;
13562
13563 g.GroupStack.pop_back();
13564 if (g.DebugShowGroupRects)
13565 window->DrawList->AddRect(group_bb.Min, group_bb.Max, IM_COL32(255, 0, 255, 255)); // [Debug]
13566}
13567
13568//-----------------------------------------------------------------------------
13569// [SECTION] SCROLLING
13570//-----------------------------------------------------------------------------
13571
13572// Helper to snap on edges when aiming at an item very close to the edge,
13573// So the difference between WindowPadding and ItemSpacing will be in the visible area after scrolling.
13574// When we refactor the scrolling API this may be configurable with a flag?
13575// Note that the effect for this won't be visible on X axis with default Style settings as WindowPadding.x ==
13576// ItemSpacing.x by default.
13577static float CalcScrollEdgeSnap(float target, float snap_min, float snap_max, float snap_threshold, float center_ratio)
13578{
13579 if (target <= snap_min + snap_threshold)
13580 return ImLerp(snap_min, target, center_ratio);
13581 if (target >= snap_max - snap_threshold)
13582 return ImLerp(target, snap_max, center_ratio);
13583 return target;
13584}
13585
13586static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow *window)
13587{
13588 ImVec2 scroll = window->Scroll;
13589 ImVec2 decoration_size(window->DecoOuterSizeX1 + window->DecoInnerSizeX1 + window->DecoOuterSizeX2,
13590 window->DecoOuterSizeY1 + window->DecoInnerSizeY1 + window->DecoOuterSizeY2);
13591 for (int axis = 0; axis < 2; axis++)
13592 {
13593 if (window->ScrollTarget[axis] < FLT_MAX)
13594 {
13595 float center_ratio = window->ScrollTargetCenterRatio[axis];
13596 float scroll_target = window->ScrollTarget[axis];
13597 if (window->ScrollTargetEdgeSnapDist[axis] > 0.0f)
13598 {
13599 float snap_min = 0.0f;
13600 float snap_max = window->ScrollMax[axis] + window->SizeFull[axis] - decoration_size[axis];
13601 scroll_target = CalcScrollEdgeSnap(scroll_target, snap_min, snap_max,
13602 window->ScrollTargetEdgeSnapDist[axis], center_ratio);
13603 }
13604 scroll[axis] = scroll_target - center_ratio * (window->SizeFull[axis] - decoration_size[axis]);
13605 }
13606 scroll[axis] = IM_ROUND(ImMax(scroll[axis], 0.0f));
13607 if (!window->Collapsed && !window->SkipItems)
13608 scroll[axis] = ImMin(scroll[axis], window->ScrollMax[axis]);
13609 }
13610 return scroll;
13611}
13612
13613void ImGui::ScrollToItem(ImGuiScrollFlags flags)
13614{
13615 ImGuiContext &g = *GImGui;
13616 ImGuiWindow *window = g.CurrentWindow;
13617 ScrollToRectEx(window, g.LastItemData.NavRect, flags);
13618}
13619
13620void ImGui::ScrollToRect(ImGuiWindow *window, const ImRect &item_rect, ImGuiScrollFlags flags)
13621{
13622 ScrollToRectEx(window, item_rect, flags);
13623}
13624
13625// Scroll to keep newly navigated item fully into view
13626ImVec2 ImGui::ScrollToRectEx(ImGuiWindow *window, const ImRect &item_rect, ImGuiScrollFlags flags)
13627{
13628 ImGuiContext &g = *GImGui;
13629 ImRect scroll_rect(window->InnerRect.Min - ImVec2(1, 1), window->InnerRect.Max + ImVec2(1, 1));
13630 scroll_rect.Min.x = ImMin(scroll_rect.Min.x + window->DecoInnerSizeX1, scroll_rect.Max.x);
13631 scroll_rect.Min.y = ImMin(scroll_rect.Min.y + window->DecoInnerSizeY1, scroll_rect.Max.y);
13632 // GetForegroundDrawList(window)->AddRect(item_rect.Min, item_rect.Max, IM_COL32(255,0,0,255), 0.0f, 0, 5.0f); //
13633 // [DEBUG] GetForegroundDrawList(window)->AddRect(scroll_rect.Min, scroll_rect.Max, IM_COL32_WHITE); // [DEBUG]
13634
13635 // Check that only one behavior is selected per axis
13636 IM_ASSERT((flags & ImGuiScrollFlags_MaskX_) == 0 || ImIsPowerOfTwo(flags & ImGuiScrollFlags_MaskX_));
13637 IM_ASSERT((flags & ImGuiScrollFlags_MaskY_) == 0 || ImIsPowerOfTwo(flags & ImGuiScrollFlags_MaskY_));
13638
13639 // Defaults
13640 ImGuiScrollFlags in_flags = flags;
13641 if ((flags & ImGuiScrollFlags_MaskX_) == 0 && window->ScrollbarX)
13642 flags |= ImGuiScrollFlags_KeepVisibleEdgeX;
13643 if ((flags & ImGuiScrollFlags_MaskY_) == 0)
13644 flags |= window->Appearing ? ImGuiScrollFlags_AlwaysCenterY : ImGuiScrollFlags_KeepVisibleEdgeY;
13645
13646 const bool fully_visible_x = item_rect.Min.x >= scroll_rect.Min.x && item_rect.Max.x <= scroll_rect.Max.x;
13647 const bool fully_visible_y = item_rect.Min.y >= scroll_rect.Min.y && item_rect.Max.y <= scroll_rect.Max.y;
13648 const bool can_be_fully_visible_x =
13649 (item_rect.GetWidth() + g.Style.ItemSpacing.x * 2.0f) <= scroll_rect.GetWidth() ||
13650 (window->AutoFitFramesX > 0) || (window->Flags & ImGuiWindowFlags_AlwaysAutoResize) != 0;
13651 const bool can_be_fully_visible_y =
13652 (item_rect.GetHeight() + g.Style.ItemSpacing.y * 2.0f) <= scroll_rect.GetHeight() ||
13653 (window->AutoFitFramesY > 0) || (window->Flags & ImGuiWindowFlags_AlwaysAutoResize) != 0;
13654
13655 if ((flags & ImGuiScrollFlags_KeepVisibleEdgeX) && !fully_visible_x)
13656 {
13657 if (item_rect.Min.x < scroll_rect.Min.x || !can_be_fully_visible_x)
13658 SetScrollFromPosX(window, item_rect.Min.x - g.Style.ItemSpacing.x - window->Pos.x, 0.0f);
13659 else if (item_rect.Max.x >= scroll_rect.Max.x)
13660 SetScrollFromPosX(window, item_rect.Max.x + g.Style.ItemSpacing.x - window->Pos.x, 1.0f);
13661 }
13662 else if (((flags & ImGuiScrollFlags_KeepVisibleCenterX) && !fully_visible_x) ||
13663 (flags & ImGuiScrollFlags_AlwaysCenterX))
13664 {
13665 if (can_be_fully_visible_x)
13666 SetScrollFromPosX(window, ImTrunc((item_rect.Min.x + item_rect.Max.x) * 0.5f) - window->Pos.x, 0.5f);
13667 else
13668 SetScrollFromPosX(window, item_rect.Min.x - window->Pos.x, 0.0f);
13669 }
13670
13671 if ((flags & ImGuiScrollFlags_KeepVisibleEdgeY) && !fully_visible_y)
13672 {
13673 if (item_rect.Min.y < scroll_rect.Min.y || !can_be_fully_visible_y)
13674 SetScrollFromPosY(window, item_rect.Min.y - g.Style.ItemSpacing.y - window->Pos.y, 0.0f);
13675 else if (item_rect.Max.y >= scroll_rect.Max.y)
13676 SetScrollFromPosY(window, item_rect.Max.y + g.Style.ItemSpacing.y - window->Pos.y, 1.0f);
13677 }
13678 else if (((flags & ImGuiScrollFlags_KeepVisibleCenterY) && !fully_visible_y) ||
13679 (flags & ImGuiScrollFlags_AlwaysCenterY))
13680 {
13681 if (can_be_fully_visible_y)
13682 SetScrollFromPosY(window, ImTrunc((item_rect.Min.y + item_rect.Max.y) * 0.5f) - window->Pos.y, 0.5f);
13683 else
13684 SetScrollFromPosY(window, item_rect.Min.y - window->Pos.y, 0.0f);
13685 }
13686
13687 ImVec2 next_scroll = CalcNextScrollFromScrollTargetAndClamp(window);
13688 ImVec2 delta_scroll = next_scroll - window->Scroll;
13689
13690 // Also scroll parent window to keep us into view if necessary
13691 if (!(flags & ImGuiScrollFlags_NoScrollParent) && (window->Flags & ImGuiWindowFlags_ChildWindow))
13692 {
13693 // FIXME-SCROLL: May be an option?
13694 if ((in_flags & (ImGuiScrollFlags_AlwaysCenterX | ImGuiScrollFlags_KeepVisibleCenterX)) != 0)
13695 in_flags = (in_flags & ~ImGuiScrollFlags_MaskX_) | ImGuiScrollFlags_KeepVisibleEdgeX;
13696 if ((in_flags & (ImGuiScrollFlags_AlwaysCenterY | ImGuiScrollFlags_KeepVisibleCenterY)) != 0)
13697 in_flags = (in_flags & ~ImGuiScrollFlags_MaskY_) | ImGuiScrollFlags_KeepVisibleEdgeY;
13698 delta_scroll += ScrollToRectEx(window->ParentWindow,
13699 ImRect(item_rect.Min - delta_scroll, item_rect.Max - delta_scroll), in_flags);
13700 }
13701
13702 return delta_scroll;
13703}
13704
13705float ImGui::GetScrollX()
13706{
13707 ImGuiWindow *window = GImGui->CurrentWindow;
13708 return window->Scroll.x;
13709}
13710
13711float ImGui::GetScrollY()
13712{
13713 ImGuiWindow *window = GImGui->CurrentWindow;
13714 return window->Scroll.y;
13715}
13716
13717float ImGui::GetScrollMaxX()
13718{
13719 ImGuiWindow *window = GImGui->CurrentWindow;
13720 return window->ScrollMax.x;
13721}
13722
13723float ImGui::GetScrollMaxY()
13724{
13725 ImGuiWindow *window = GImGui->CurrentWindow;
13726 return window->ScrollMax.y;
13727}
13728
13729void ImGui::SetScrollX(ImGuiWindow *window, float scroll_x)
13730{
13731 window->ScrollTarget.x = scroll_x;
13732 window->ScrollTargetCenterRatio.x = 0.0f;
13733 window->ScrollTargetEdgeSnapDist.x = 0.0f;
13734}
13735
13736void ImGui::SetScrollY(ImGuiWindow *window, float scroll_y)
13737{
13738 window->ScrollTarget.y = scroll_y;
13739 window->ScrollTargetCenterRatio.y = 0.0f;
13740 window->ScrollTargetEdgeSnapDist.y = 0.0f;
13741}
13742
13743void ImGui::SetScrollX(float scroll_x)
13744{
13745 ImGuiContext &g = *GImGui;
13746 SetScrollX(g.CurrentWindow, scroll_x);
13747}
13748
13749void ImGui::SetScrollY(float scroll_y)
13750{
13751 ImGuiContext &g = *GImGui;
13752 SetScrollY(g.CurrentWindow, scroll_y);
13753}
13754
13755// Note that a local position will vary depending on initial scroll value,
13756// This is a little bit confusing so bear with us:
13757// - local_pos = (absolution_pos - window->Pos)
13758// - So local_x/local_y are 0.0f for a position at the upper-left corner of a window,
13759// and generally local_x/local_y are >(padding+decoration) && <(size-padding-decoration) when in the visible area.
13760// - They mostly exist because of legacy API.
13761// Following the rules above, when trying to work with scrolling code, consider that:
13762// - SetScrollFromPosY(0.0f) == SetScrollY(0.0f + scroll.y) == has no effect!
13763// - SetScrollFromPosY(-scroll.y) == SetScrollY(-scroll.y + scroll.y) == SetScrollY(0.0f) == reset scroll. Of course
13764// writing SetScrollY(0.0f) directly then makes more sense
13765// We store a target position so centering and clamping can occur on the next frame when we are guaranteed to have a
13766// known window size
13767void ImGui::SetScrollFromPosX(ImGuiWindow *window, float local_x, float center_x_ratio)
13768{
13769 IM_ASSERT(center_x_ratio >= 0.0f && center_x_ratio <= 1.0f);
13770 window->ScrollTarget.x = IM_TRUNC(local_x - window->DecoOuterSizeX1 - window->DecoInnerSizeX1 +
13771 window->Scroll.x); // Convert local position to scroll offset
13772 window->ScrollTargetCenterRatio.x = center_x_ratio;
13773 window->ScrollTargetEdgeSnapDist.x = 0.0f;
13774}
13775
13776void ImGui::SetScrollFromPosY(ImGuiWindow *window, float local_y, float center_y_ratio)
13777{
13778 IM_ASSERT(center_y_ratio >= 0.0f && center_y_ratio <= 1.0f);
13779 window->ScrollTarget.y = IM_TRUNC(local_y - window->DecoOuterSizeY1 - window->DecoInnerSizeY1 +
13780 window->Scroll.y); // Convert local position to scroll offset
13781 window->ScrollTargetCenterRatio.y = center_y_ratio;
13782 window->ScrollTargetEdgeSnapDist.y = 0.0f;
13783}
13784
13785void ImGui::SetScrollFromPosX(float local_x, float center_x_ratio)
13786{
13787 ImGuiContext &g = *GImGui;
13788 SetScrollFromPosX(g.CurrentWindow, local_x, center_x_ratio);
13789}
13790
13791void ImGui::SetScrollFromPosY(float local_y, float center_y_ratio)
13792{
13793 ImGuiContext &g = *GImGui;
13794 SetScrollFromPosY(g.CurrentWindow, local_y, center_y_ratio);
13795}
13796
13797// center_x_ratio: 0.0f left of last item, 0.5f horizontal center of last item, 1.0f right of last item.
13798void ImGui::SetScrollHereX(float center_x_ratio)
13799{
13800 ImGuiContext &g = *GImGui;
13801 ImGuiWindow *window = g.CurrentWindow;
13802 float spacing_x = ImMax(window->WindowPadding.x, g.Style.ItemSpacing.x);
13803 float target_pos_x =
13804 ImLerp(g.LastItemData.Rect.Min.x - spacing_x, g.LastItemData.Rect.Max.x + spacing_x, center_x_ratio);
13805 SetScrollFromPosX(window, target_pos_x - window->Pos.x, center_x_ratio); // Convert from absolute to local pos
13806
13807 // Tweak: snap on edges when aiming at an item very close to the edge
13808 window->ScrollTargetEdgeSnapDist.x = ImMax(0.0f, window->WindowPadding.x - spacing_x);
13809}
13810
13811// center_y_ratio: 0.0f top of last item, 0.5f vertical center of last item, 1.0f bottom of last item.
13812void ImGui::SetScrollHereY(float center_y_ratio)
13813{
13814 ImGuiContext &g = *GImGui;
13815 ImGuiWindow *window = g.CurrentWindow;
13816 float spacing_y = ImMax(window->WindowPadding.y, g.Style.ItemSpacing.y);
13817 float target_pos_y = ImLerp(window->DC.CursorPosPrevLine.y - spacing_y,
13818 window->DC.CursorPosPrevLine.y + window->DC.PrevLineSize.y + spacing_y, center_y_ratio);
13819 SetScrollFromPosY(window, target_pos_y - window->Pos.y, center_y_ratio); // Convert from absolute to local pos
13820
13821 // Tweak: snap on edges when aiming at an item very close to the edge
13822 window->ScrollTargetEdgeSnapDist.y = ImMax(0.0f, window->WindowPadding.y - spacing_y);
13823}
13824
13825//-----------------------------------------------------------------------------
13826// [SECTION] TOOLTIPS
13827//-----------------------------------------------------------------------------
13828
13829bool ImGui::BeginTooltip()
13830{
13831 return BeginTooltipEx(ImGuiTooltipFlags_None, ImGuiWindowFlags_None);
13832}
13833
13834bool ImGui::BeginItemTooltip()
13835{
13836 if (!IsItemHovered(ImGuiHoveredFlags_ForTooltip))
13837 return false;
13838 return BeginTooltipEx(ImGuiTooltipFlags_None, ImGuiWindowFlags_None);
13839}
13840
13841bool ImGui::BeginTooltipEx(ImGuiTooltipFlags tooltip_flags, ImGuiWindowFlags extra_window_flags)
13842{
13843 ImGuiContext &g = *GImGui;
13844
13845 const bool is_dragdrop_tooltip = g.DragDropWithinSource || g.DragDropWithinTarget;
13846 if (is_dragdrop_tooltip)
13847 {
13848 // Drag and Drop tooltips are positioning differently than other tooltips:
13849 // - offset visibility to increase visibility around mouse.
13850 // - never clamp within outer viewport boundary.
13851 // We call SetNextWindowPos() to enforce position and disable clamping.
13852 // See FindBestWindowPosForPopup() for positioning logic of other tooltips (not drag and drop ones).
13853 // ImVec2 tooltip_pos = g.IO.MousePos - g.ActiveIdClickOffset - g.Style.WindowPadding;
13854 const bool is_touchscreen = (g.IO.MouseSource == ImGuiMouseSource_TouchScreen);
13855 if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasPos) == 0)
13856 {
13857 ImVec2 tooltip_pos = is_touchscreen
13858 ? (g.IO.MousePos + TOOLTIP_DEFAULT_OFFSET_TOUCH * g.Style.MouseCursorScale)
13859 : (g.IO.MousePos + TOOLTIP_DEFAULT_OFFSET_MOUSE * g.Style.MouseCursorScale);
13860 ImVec2 tooltip_pivot = is_touchscreen ? TOOLTIP_DEFAULT_PIVOT_TOUCH : ImVec2(0.0f, 0.0f);
13861 SetNextWindowPos(tooltip_pos, ImGuiCond_None, tooltip_pivot);
13862 }
13863
13864 SetNextWindowBgAlpha(g.Style.Colors[ImGuiCol_PopupBg].w * 0.60f);
13865 // PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * 0.60f); // This would be nice but e.g ColorButton with
13866 // checkboard has issue with transparent colors :(
13867 tooltip_flags |= ImGuiTooltipFlags_OverridePrevious;
13868 }
13869
13870 const char *window_name_template = is_dragdrop_tooltip ? "##Tooltip_DragDrop_%02d" : "##Tooltip_%02d";
13871 char window_name[32];
13872 ImFormatString(window_name, IM_ARRAYSIZE(window_name), window_name_template, g.TooltipOverrideCount);
13873 if ((tooltip_flags & ImGuiTooltipFlags_OverridePrevious) && g.TooltipPreviousWindow != NULL &&
13874 g.TooltipPreviousWindow->Active)
13875 {
13876 // Hide previous tooltip from being displayed. We can't easily "reset" the content of a window so we create a
13877 // new one.
13878 // IMGUI_DEBUG_LOG("[tooltip] '%s' already active, using +1 for this frame\n", window_name);
13879 SetWindowHiddenAndSkipItemsForCurrentFrame(g.TooltipPreviousWindow);
13880 ImFormatString(window_name, IM_ARRAYSIZE(window_name), window_name_template, ++g.TooltipOverrideCount);
13881 }
13882
13883 ImGuiWindowFlags flags = ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoTitleBar |
13884 ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings |
13885 ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDocking;
13886 Begin(window_name, NULL, flags | extra_window_flags);
13887 // 2023-03-09: Added bool return value to the API, but currently always returning true.
13888 // If this ever returns false we need to update BeginDragDropSource() accordingly.
13889 // if (!ret)
13890 // End();
13891 // return ret;
13892 return true;
13893}
13894
13895void ImGui::EndTooltip()
13896{
13897 IM_ASSERT(GetCurrentWindowRead()->Flags & ImGuiWindowFlags_Tooltip); // Mismatched BeginTooltip()/EndTooltip() calls
13898 End();
13899}
13900
13901void ImGui::SetTooltip(const char *fmt, ...)
13902{
13903 va_list args;
13904 va_start(args, fmt);
13905 SetTooltipV(fmt, args);
13906 va_end(args);
13907}
13908
13909void ImGui::SetTooltipV(const char *fmt, va_list args)
13910{
13911 if (!BeginTooltipEx(ImGuiTooltipFlags_OverridePrevious, ImGuiWindowFlags_None))
13912 return;
13913 TextV(fmt, args);
13914 EndTooltip();
13915}
13916
13917// Shortcut to use 'style.HoverFlagsForTooltipMouse' or 'style.HoverFlagsForTooltipNav'.
13918// Defaults to == ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayShort when using the mouse.
13919void ImGui::SetItemTooltip(const char *fmt, ...)
13920{
13921 va_list args;
13922 va_start(args, fmt);
13923 if (IsItemHovered(ImGuiHoveredFlags_ForTooltip))
13924 SetTooltipV(fmt, args);
13925 va_end(args);
13926}
13927
13928void ImGui::SetItemTooltipV(const char *fmt, va_list args)
13929{
13930 if (IsItemHovered(ImGuiHoveredFlags_ForTooltip))
13931 SetTooltipV(fmt, args);
13932}
13933
13934//-----------------------------------------------------------------------------
13935// [SECTION] POPUPS
13936//-----------------------------------------------------------------------------
13937
13938// Supported flags: ImGuiPopupFlags_AnyPopupId, ImGuiPopupFlags_AnyPopupLevel
13939bool ImGui::IsPopupOpen(ImGuiID id, ImGuiPopupFlags popup_flags)
13940{
13941 ImGuiContext &g = *GImGui;
13942 if (popup_flags & ImGuiPopupFlags_AnyPopupId)
13943 {
13944 // Return true if any popup is open at the current BeginPopup() level of the popup stack
13945 // This may be used to e.g. test for another popups already opened to handle popups priorities at the same
13946 // level.
13947 IM_ASSERT(id == 0);
13948 if (popup_flags & ImGuiPopupFlags_AnyPopupLevel)
13949 return g.OpenPopupStack.Size > 0;
13950 else
13951 return g.OpenPopupStack.Size > g.BeginPopupStack.Size;
13952 }
13953 else
13954 {
13955 if (popup_flags & ImGuiPopupFlags_AnyPopupLevel)
13956 {
13957 // Return true if the popup is open anywhere in the popup stack
13958 for (ImGuiPopupData &popup_data : g.OpenPopupStack)
13959 if (popup_data.PopupId == id)
13960 return true;
13961 return false;
13962 }
13963 else
13964 {
13965 // Return true if the popup is open at the current BeginPopup() level of the popup stack (this is the
13966 // most-common query)
13967 return g.OpenPopupStack.Size > g.BeginPopupStack.Size &&
13968 g.OpenPopupStack[g.BeginPopupStack.Size].PopupId == id;
13969 }
13970 }
13971}
13972
13973bool ImGui::IsPopupOpen(const char *str_id, ImGuiPopupFlags popup_flags)
13974{
13975 ImGuiContext &g = *GImGui;
13976 ImGuiID id = (popup_flags & ImGuiPopupFlags_AnyPopupId) ? 0 : g.CurrentWindow->GetID(str_id);
13977 if ((popup_flags & ImGuiPopupFlags_AnyPopupLevel) && id != 0)
13978 IM_ASSERT(
13979 0 && "Cannot use IsPopupOpen() with a string id and ImGuiPopupFlags_AnyPopupLevel."); // But non-string
13980 // version is legal
13981 // and used internally
13982 return IsPopupOpen(id, popup_flags);
13983}
13984
13985// Also see FindBlockingModal(NULL)
13986ImGuiWindow *ImGui::GetTopMostPopupModal()
13987{
13988 ImGuiContext &g = *GImGui;
13989 for (int n = g.OpenPopupStack.Size - 1; n >= 0; n--)
13990 if (ImGuiWindow *popup = g.OpenPopupStack.Data[n].Window)
13991 if (popup->Flags & ImGuiWindowFlags_Modal)
13992 return popup;
13993 return NULL;
13994}
13995
13996// See Demo->Stacked Modal to confirm what this is for.
13997ImGuiWindow *ImGui::GetTopMostAndVisiblePopupModal()
13998{
13999 ImGuiContext &g = *GImGui;
14000 for (int n = g.OpenPopupStack.Size - 1; n >= 0; n--)
14001 if (ImGuiWindow *popup = g.OpenPopupStack.Data[n].Window)
14002 if ((popup->Flags & ImGuiWindowFlags_Modal) && IsWindowActiveAndVisible(popup))
14003 return popup;
14004 return NULL;
14005}
14006
14007// When a modal popup is open, newly created windows that want focus (i.e. are not popups and do not specify
14008// ImGuiWindowFlags_NoFocusOnAppearing) should be positioned behind that modal window, unless the window was created
14009// inside the modal begin-stack. In case of multiple stacked modals newly created window honors begin stack order and
14010// does not go below its own modal parent.
14011// - WindowA // FindBlockingModal() returns Modal1
14012// - WindowB // .. returns Modal1
14013// - Modal1 // .. returns Modal2
14014// - WindowC // .. returns Modal2
14015// - WindowD // .. returns Modal2
14016// - Modal2 // .. returns Modal2
14017// - WindowE // .. returns NULL
14018// Notes:
14019// - FindBlockingModal(NULL) == NULL is generally equivalent to GetTopMostPopupModal() == NULL.
14020// Only difference is here we check for ->Active/WasActive but it may be unnecessary.
14021ImGuiWindow *ImGui::FindBlockingModal(ImGuiWindow *window)
14022{
14023 ImGuiContext &g = *GImGui;
14024 if (g.OpenPopupStack.Size <= 0)
14025 return NULL;
14026
14027 // Find a modal that has common parent with specified window. Specified window should be positioned behind that
14028 // modal.
14029 for (ImGuiPopupData &popup_data : g.OpenPopupStack)
14030 {
14031 ImGuiWindow *popup_window = popup_data.Window;
14032 if (popup_window == NULL || !(popup_window->Flags & ImGuiWindowFlags_Modal))
14033 continue;
14034 if (!popup_window->Active &&
14035 !popup_window->WasActive) // Check WasActive, because this code may run before popup renders on current
14036 // frame, also check Active to handle newly created windows.
14037 continue;
14038 if (window ==
14039 NULL) // FindBlockingModal(NULL) test for if FocusWindow(NULL) is naturally possible via a mouse click.
14040 return popup_window;
14041 if (IsWindowWithinBeginStackOf(window, popup_window)) // Window may be over modal
14042 continue;
14043 return popup_window; // Place window right below first block modal
14044 }
14045 return NULL;
14046}
14047
14048void ImGui::OpenPopup(const char *str_id, ImGuiPopupFlags popup_flags)
14049{
14050 ImGuiContext &g = *GImGui;
14051 ImGuiID id = g.CurrentWindow->GetID(str_id);
14052 IMGUI_DEBUG_LOG_POPUP("[popup] OpenPopup(\"%s\" -> 0x%08X)\n", str_id, id);
14053 OpenPopupEx(id, popup_flags);
14054}
14055
14056void ImGui::OpenPopup(ImGuiID id, ImGuiPopupFlags popup_flags)
14057{
14058 OpenPopupEx(id, popup_flags);
14059}
14060
14061// Mark popup as open (toggle toward open state).
14062// Popups are closed when user click outside, or activate a pressable item, or CloseCurrentPopup() is called within a
14063// BeginPopup()/EndPopup() block. Popup identifiers are relative to the current ID-stack (so OpenPopup and BeginPopup
14064// needs to be at the same level). One open popup per level of the popup hierarchy (NB: when assigning we reset the
14065// Window member of ImGuiPopupRef to NULL)
14066void ImGui::OpenPopupEx(ImGuiID id, ImGuiPopupFlags popup_flags)
14067{
14068 ImGuiContext &g = *GImGui;
14069 ImGuiWindow *parent_window = g.CurrentWindow;
14070 const int current_stack_size = g.BeginPopupStack.Size;
14071
14072 if (popup_flags & ImGuiPopupFlags_NoOpenOverExistingPopup)
14073 if (IsPopupOpen((ImGuiID)0, ImGuiPopupFlags_AnyPopupId))
14074 return;
14075
14077 popup_ref; // Tagged as new ref as Window will be set back to NULL if we write this into OpenPopupStack.
14078 popup_ref.PopupId = id;
14079 popup_ref.Window = NULL;
14080 popup_ref.RestoreNavWindow =
14081 g.NavWindow; // When popup closes focus may be restored to NavWindow (depend on window type).
14082 popup_ref.OpenFrameCount = g.FrameCount;
14083 popup_ref.OpenParentId = parent_window->IDStack.back();
14084 popup_ref.OpenPopupPos = NavCalcPreferredRefPos();
14085 popup_ref.OpenMousePos = IsMousePosValid(&g.IO.MousePos) ? g.IO.MousePos : popup_ref.OpenPopupPos;
14086
14087 IMGUI_DEBUG_LOG_POPUP("[popup] OpenPopupEx(0x%08X)\n", id);
14088 if (g.OpenPopupStack.Size < current_stack_size + 1)
14089 {
14090 g.OpenPopupStack.push_back(popup_ref);
14091 }
14092 else
14093 {
14094 // Gently handle the user mistakenly calling OpenPopup() every frames: it is likely a programming mistake!
14095 // However, if we were to run the regular code path, the ui would become completely unusable because the popup
14096 // will always be in hidden-while-calculating-size state _while_ claiming focus. Which is extremely confusing
14097 // situation for the programmer. Instead, for successive frames calls to OpenPopup(), we silently avoid
14098 // reopening even if ImGuiPopupFlags_NoReopen is not specified.
14099 bool keep_existing = false;
14100 if (g.OpenPopupStack[current_stack_size].PopupId == id)
14101 if ((g.OpenPopupStack[current_stack_size].OpenFrameCount == g.FrameCount - 1) ||
14102 (popup_flags & ImGuiPopupFlags_NoReopen))
14103 keep_existing = true;
14104 if (keep_existing)
14105 {
14106 // No reopen
14107 g.OpenPopupStack[current_stack_size].OpenFrameCount = popup_ref.OpenFrameCount;
14108 }
14109 else
14110 {
14111 // Reopen: close child popups if any, then flag popup for open/reopen (set position, focus, init navigation)
14112 ClosePopupToLevel(current_stack_size, true);
14113 g.OpenPopupStack.push_back(popup_ref);
14114 }
14115
14116 // When reopening a popup we first refocus its parent, otherwise if its parent is itself a popup it would get
14117 // closed by ClosePopupsOverWindow(). This is equivalent to what ClosePopupToLevel() does.
14118 // if (g.OpenPopupStack[current_stack_size].PopupId == id)
14119 // FocusWindow(parent_window);
14120 }
14121}
14122
14123// When popups are stacked, clicking on a lower level popups puts focus back to it and close popups above it.
14124// This function closes any popups that are over 'ref_window'.
14125void ImGui::ClosePopupsOverWindow(ImGuiWindow *ref_window, bool restore_focus_to_window_under_popup)
14126{
14127 ImGuiContext &g = *GImGui;
14128 if (g.OpenPopupStack.Size == 0)
14129 return;
14130
14131 // Don't close our own child popup windows.
14132 // IMGUI_DEBUG_LOG_POPUP("[popup] ClosePopupsOverWindow(\"%s\") restore_under=%d\n", ref_window ? ref_window->Name :
14133 // "<NULL>", restore_focus_to_window_under_popup);
14134 int popup_count_to_keep = 0;
14135 if (ref_window)
14136 {
14137 // Find the highest popup which is a descendant of the reference window (generally reference window = NavWindow)
14138 for (; popup_count_to_keep < g.OpenPopupStack.Size; popup_count_to_keep++)
14139 {
14140 ImGuiPopupData &popup = g.OpenPopupStack[popup_count_to_keep];
14141 if (!popup.Window)
14142 continue;
14143 IM_ASSERT((popup.Window->Flags & ImGuiWindowFlags_Popup) != 0);
14144
14145 // Trim the stack unless the popup is a direct parent of the reference window (the reference window is often
14146 // the NavWindow)
14147 // - Clicking/Focusing Window2 won't close Popup1:
14148 // Window -> Popup1 -> Window2(Ref)
14149 // - Clicking/focusing Popup1 will close Popup2 and Popup3:
14150 // Window -> Popup1(Ref) -> Popup2 -> Popup3
14151 // - Each popups may contain child windows, which is why we compare ->RootWindowDockTree!
14152 // Window -> Popup1 -> Popup1_Child -> Popup2 -> Popup2_Child
14153 // We step through every popup from bottom to top to validate their position relative to reference window.
14154 bool ref_window_is_descendent_of_popup = false;
14155 for (int n = popup_count_to_keep; n < g.OpenPopupStack.Size; n++)
14156 if (ImGuiWindow *popup_window = g.OpenPopupStack[n].Window)
14157 // if (popup_window->RootWindowDockTree == ref_window->RootWindowDockTree) // FIXME-MERGE
14158 if (IsWindowWithinBeginStackOf(ref_window, popup_window))
14159 {
14160 ref_window_is_descendent_of_popup = true;
14161 break;
14162 }
14163 if (!ref_window_is_descendent_of_popup)
14164 break;
14165 }
14166 }
14167 if (popup_count_to_keep <
14168 g.OpenPopupStack
14169 .Size) // This test is not required but it allows to set a convenient breakpoint on the statement below
14170 {
14171 IMGUI_DEBUG_LOG_POPUP("[popup] ClosePopupsOverWindow(\"%s\")\n", ref_window ? ref_window->Name : "<NULL>");
14172 ClosePopupToLevel(popup_count_to_keep, restore_focus_to_window_under_popup);
14173 }
14174}
14175
14176void ImGui::ClosePopupsExceptModals()
14177{
14178 ImGuiContext &g = *GImGui;
14179
14180 int popup_count_to_keep;
14181 for (popup_count_to_keep = g.OpenPopupStack.Size; popup_count_to_keep > 0; popup_count_to_keep--)
14182 {
14183 ImGuiWindow *window = g.OpenPopupStack[popup_count_to_keep - 1].Window;
14184 if (!window || (window->Flags & ImGuiWindowFlags_Modal))
14185 break;
14186 }
14187 if (popup_count_to_keep <
14188 g.OpenPopupStack
14189 .Size) // This test is not required but it allows to set a convenient breakpoint on the statement below
14190 ClosePopupToLevel(popup_count_to_keep, true);
14191}
14192
14193void ImGui::ClosePopupToLevel(int remaining, bool restore_focus_to_window_under_popup)
14194{
14195 ImGuiContext &g = *GImGui;
14196 IMGUI_DEBUG_LOG_POPUP("[popup] ClosePopupToLevel(%d), restore_under=%d\n", remaining,
14197 restore_focus_to_window_under_popup);
14198 IM_ASSERT(remaining >= 0 && remaining < g.OpenPopupStack.Size);
14199 if (g.DebugLogFlags & ImGuiDebugLogFlags_EventPopup)
14200 for (int n = remaining; n < g.OpenPopupStack.Size; n++)
14201 IMGUI_DEBUG_LOG_POPUP("[popup] - Closing PopupID 0x%08X Window \"%s\"\n", g.OpenPopupStack[n].PopupId,
14202 g.OpenPopupStack[n].Window ? g.OpenPopupStack[n].Window->Name : NULL);
14203
14204 // Trim open popup stack
14205 ImGuiPopupData prev_popup = g.OpenPopupStack[remaining];
14206 g.OpenPopupStack.resize(remaining);
14207
14208 // Restore focus (unless popup window was not yet submitted, and didn't have a chance to take focus anyhow. See
14209 // #7325 for an edge case)
14210 if (restore_focus_to_window_under_popup && prev_popup.Window)
14211 {
14212 ImGuiWindow *popup_window = prev_popup.Window;
14213 ImGuiWindow *focus_window = (popup_window->Flags & ImGuiWindowFlags_ChildMenu) ? popup_window->ParentWindow
14214 : prev_popup.RestoreNavWindow;
14215 if (focus_window && !focus_window->WasActive)
14216 FocusTopMostWindowUnderOne(popup_window, NULL, NULL,
14217 ImGuiFocusRequestFlags_RestoreFocusedChild); // Fallback
14218 else
14219 FocusWindow(focus_window, (g.NavLayer == ImGuiNavLayer_Main) ? ImGuiFocusRequestFlags_RestoreFocusedChild
14220 : ImGuiFocusRequestFlags_None);
14221 }
14222}
14223
14224// Close the popup we have begin-ed into.
14225void ImGui::CloseCurrentPopup()
14226{
14227 ImGuiContext &g = *GImGui;
14228 int popup_idx = g.BeginPopupStack.Size - 1;
14229 if (popup_idx < 0 || popup_idx >= g.OpenPopupStack.Size ||
14230 g.BeginPopupStack[popup_idx].PopupId != g.OpenPopupStack[popup_idx].PopupId)
14231 return;
14232
14233 // Closing a menu closes its top-most parent popup (unless a modal)
14234 while (popup_idx > 0)
14235 {
14236 ImGuiWindow *popup_window = g.OpenPopupStack[popup_idx].Window;
14237 ImGuiWindow *parent_popup_window = g.OpenPopupStack[popup_idx - 1].Window;
14238 bool close_parent = false;
14239 if (popup_window && (popup_window->Flags & ImGuiWindowFlags_ChildMenu))
14240 if (parent_popup_window && !(parent_popup_window->Flags & ImGuiWindowFlags_MenuBar))
14241 close_parent = true;
14242 if (!close_parent)
14243 break;
14244 popup_idx--;
14245 }
14246 IMGUI_DEBUG_LOG_POPUP("[popup] CloseCurrentPopup %d -> %d\n", g.BeginPopupStack.Size - 1, popup_idx);
14247 ClosePopupToLevel(popup_idx, true);
14248
14249 // A common pattern is to close a popup when selecting a menu item/selectable that will open another window.
14250 // To improve this usage pattern, we avoid nav highlight for a single frame in the parent window.
14251 // Similarly, we could avoid mouse hover highlight in this window but it is less visually problematic.
14252 if (ImGuiWindow *window = g.NavWindow)
14253 window->DC.NavHideHighlightOneFrame = true;
14254}
14255
14256// Attention! BeginPopup() adds default flags when calling BeginPopupEx()!
14257bool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_window_flags)
14258{
14259 ImGuiContext &g = *GImGui;
14260 if (!IsPopupOpen(id, ImGuiPopupFlags_None))
14261 {
14262 g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
14263 return false;
14264 }
14265
14266 char name[20];
14267 IM_ASSERT((extra_window_flags & ImGuiWindowFlags_ChildMenu) == 0); // Use BeginPopupMenuEx()
14268 ImFormatString(name, IM_ARRAYSIZE(name), "##Popup_%08x",
14269 id); // No recycling, so we can close/open during the same frame
14270
14271 bool is_open = Begin(name, NULL, extra_window_flags | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoDocking);
14272 if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display)
14273 EndPopup();
14274 // g.CurrentWindow->FocusRouteParentWindow = g.CurrentWindow->ParentWindowInBeginStack;
14275 return is_open;
14276}
14277
14278bool ImGui::BeginPopupMenuEx(ImGuiID id, const char *label, ImGuiWindowFlags extra_window_flags)
14279{
14280 ImGuiContext &g = *GImGui;
14281 if (!IsPopupOpen(id, ImGuiPopupFlags_None))
14282 {
14283 g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
14284 return false;
14285 }
14286
14287 char name[128];
14288 IM_ASSERT(extra_window_flags & ImGuiWindowFlags_ChildMenu);
14289 ImFormatString(name, IM_ARRAYSIZE(name), "%s###Menu_%02d", label,
14290 g.BeginMenuDepth); // Recycle windows based on depth
14291 bool is_open = Begin(name, NULL, extra_window_flags | ImGuiWindowFlags_Popup);
14292 if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display)
14293 EndPopup();
14294 // g.CurrentWindow->FocusRouteParentWindow = g.CurrentWindow->ParentWindowInBeginStack;
14295 return is_open;
14296}
14297
14298bool ImGui::BeginPopup(const char *str_id, ImGuiWindowFlags flags)
14299{
14300 ImGuiContext &g = *GImGui;
14301 if (g.OpenPopupStack.Size <= g.BeginPopupStack.Size) // Early out for performance
14302 {
14303 g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
14304 return false;
14305 }
14306 flags |= ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings;
14307 ImGuiID id = g.CurrentWindow->GetID(str_id);
14308 return BeginPopupEx(id, flags);
14309}
14310
14311// If 'p_open' is specified for a modal popup window, the popup will have a regular close button which will close the
14312// popup. Note that popup visibility status is owned by Dear ImGui (and manipulated with e.g. OpenPopup).
14313// - *p_open set back to false in BeginPopupModal() when popup is not open.
14314// - if you set *p_open to false before calling BeginPopupModal(), it will close the popup.
14315bool ImGui::BeginPopupModal(const char *name, bool *p_open, ImGuiWindowFlags flags)
14316{
14317 ImGuiContext &g = *GImGui;
14318 ImGuiWindow *window = g.CurrentWindow;
14319 const ImGuiID id = window->GetID(name);
14320 if (!IsPopupOpen(id, ImGuiPopupFlags_None))
14321 {
14322 g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
14323 if (p_open && *p_open)
14324 *p_open = false;
14325 return false;
14326 }
14327
14328 // Center modal windows by default for increased visibility
14329 // (this won't really last as settings will kick in, and is mostly for backward compatibility. user may do the same
14330 // themselves)
14331 // FIXME: Should test for (PosCond & window->SetWindowPosAllowFlags) with the upcoming window.
14332 if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasPos) == 0)
14333 {
14334 const ImGuiViewport *viewport = window->WasActive
14335 ? window->Viewport
14336 : GetMainViewport(); // FIXME-VIEWPORT: What may be our reference viewport?
14337 SetNextWindowPos(viewport->GetCenter(), ImGuiCond_FirstUseEver, ImVec2(0.5f, 0.5f));
14338 }
14339
14340 flags |= ImGuiWindowFlags_Popup | ImGuiWindowFlags_Modal | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDocking;
14341 const bool is_open = Begin(name, p_open, flags);
14342 if (!is_open ||
14343 (p_open &&
14344 !*p_open)) // NB: is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
14345 {
14346 EndPopup();
14347 if (is_open)
14348 ClosePopupToLevel(g.BeginPopupStack.Size, true);
14349 return false;
14350 }
14351 return is_open;
14352}
14353
14354void ImGui::EndPopup()
14355{
14356 ImGuiContext &g = *GImGui;
14357 ImGuiWindow *window = g.CurrentWindow;
14358 if ((window->Flags & ImGuiWindowFlags_Popup) == 0 || g.BeginPopupStack.Size == 0)
14359 {
14360 IM_ASSERT_USER_ERROR(0, "Calling EndPopup() too many times or in wrong window!");
14361 return;
14362 }
14363
14364 // Make all menus and popups wrap around for now, may need to expose that policy (e.g. focus scope could include
14365 // wrap/loop policy flags used by new move requests)
14366 if (g.NavWindow == window)
14367 NavMoveRequestTryWrapping(window, ImGuiNavMoveFlags_LoopY);
14368
14369 // Child-popups don't need to be laid out
14370 const ImGuiID backup_within_end_child_id = g.WithinEndChildID;
14371 if (window->Flags & ImGuiWindowFlags_ChildWindow)
14372 g.WithinEndChildID = window->ID;
14373 End();
14374 g.WithinEndChildID = backup_within_end_child_id;
14375}
14376
14377// Helper to open a popup if mouse button is released over the item
14378// - This is essentially the same as BeginPopupContextItem() but without the trailing BeginPopup()
14379void ImGui::OpenPopupOnItemClick(const char *str_id, ImGuiPopupFlags popup_flags)
14380{
14381 ImGuiContext &g = *GImGui;
14382 ImGuiWindow *window = g.CurrentWindow;
14383 int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_);
14384 if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup))
14385 {
14386 ImGuiID id = str_id ? window->GetID(str_id)
14387 : g.LastItemData.ID; // If user hasn't passed an ID, we can use the LastItemID. Using
14388 // LastItemID as a Popup ID won't conflict!
14389 IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item)
14390 OpenPopupEx(id, popup_flags);
14391 }
14392}
14393
14394// This is a helper to handle the simplest case of associating one named popup to one given widget.
14395// - To create a popup associated to the last item, you generally want to pass a NULL value to str_id.
14396// - To create a popup with a specific identifier, pass it in str_id.
14397// - This is useful when using using BeginPopupContextItem() on an item which doesn't have an identifier, e.g. a
14398// Text() call.
14399// - This is useful when multiple code locations may want to manipulate/open the same popup, given an explicit id.
14400// - You may want to handle the whole on user side if you have specific needs (e.g. tweaking IsItemHovered()
14401// parameters).
14402// This is essentially the same as:
14403// id = str_id ? GetID(str_id) : GetItemID();
14404// OpenPopupOnItemClick(str_id, ImGuiPopupFlags_MouseButtonRight);
14405// return BeginPopup(id);
14406// Which is essentially the same as:
14407// id = str_id ? GetID(str_id) : GetItemID();
14408// if (IsItemHovered() && IsMouseReleased(ImGuiMouseButton_Right))
14409// OpenPopup(id);
14410// return BeginPopup(id);
14411// The main difference being that this is tweaked to avoid computing the ID twice.
14412bool ImGui::BeginPopupContextItem(const char *str_id, ImGuiPopupFlags popup_flags)
14413{
14414 ImGuiContext &g = *GImGui;
14415 ImGuiWindow *window = g.CurrentWindow;
14416 if (window->SkipItems)
14417 return false;
14418 ImGuiID id =
14419 str_id ? window->GetID(str_id) : g.LastItemData.ID; // If user hasn't passed an ID, we can use the LastItemID.
14420 // Using LastItemID as a Popup ID won't conflict!
14421 IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item)
14422 int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_);
14423 if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup))
14424 OpenPopupEx(id, popup_flags);
14425 return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar |
14426 ImGuiWindowFlags_NoSavedSettings);
14427}
14428
14429bool ImGui::BeginPopupContextWindow(const char *str_id, ImGuiPopupFlags popup_flags)
14430{
14431 ImGuiContext &g = *GImGui;
14432 ImGuiWindow *window = g.CurrentWindow;
14433 if (!str_id)
14434 str_id = "window_context";
14435 ImGuiID id = window->GetID(str_id);
14436 int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_);
14437 if (IsMouseReleased(mouse_button) && IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup))
14438 if (!(popup_flags & ImGuiPopupFlags_NoOpenOverItems) || !IsAnyItemHovered())
14439 OpenPopupEx(id, popup_flags);
14440 return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar |
14441 ImGuiWindowFlags_NoSavedSettings);
14442}
14443
14444bool ImGui::BeginPopupContextVoid(const char *str_id, ImGuiPopupFlags popup_flags)
14445{
14446 ImGuiContext &g = *GImGui;
14447 ImGuiWindow *window = g.CurrentWindow;
14448 if (!str_id)
14449 str_id = "void_context";
14450 ImGuiID id = window->GetID(str_id);
14451 int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_);
14452 if (IsMouseReleased(mouse_button) && !IsWindowHovered(ImGuiHoveredFlags_AnyWindow))
14453 if (GetTopMostPopupModal() == NULL)
14454 OpenPopupEx(id, popup_flags);
14455 return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar |
14456 ImGuiWindowFlags_NoSavedSettings);
14457}
14458
14459// r_avoid = the rectangle to avoid (e.g. for tooltip it is a rectangle around the mouse cursor which we want to avoid.
14460// for popups it's a small point around the cursor.) r_outer = the visible area rectangle, minus safe area padding. If
14461// our popup size won't fit because of safe area padding we ignore it. (r_outer is usually equivalent to the viewport
14462// rectangle minus padding, but when multi-viewports are enabled and monitor
14463// information are available, it may represent the entire platform monitor from the frame of reference of the current
14464// viewport. this allows us to have tooltips/popups displayed out of the parent viewport.)
14465ImVec2 ImGui::FindBestWindowPosForPopupEx(const ImVec2 &ref_pos, const ImVec2 &size, ImGuiDir *last_dir,
14466 const ImRect &r_outer, const ImRect &r_avoid, ImGuiPopupPositionPolicy policy)
14467{
14468 ImVec2 base_pos_clamped = ImClamp(ref_pos, r_outer.Min, r_outer.Max - size);
14469 // GetForegroundDrawList()->AddRect(r_avoid.Min, r_avoid.Max, IM_COL32(255,0,0,255));
14470 // GetForegroundDrawList()->AddRect(r_outer.Min, r_outer.Max, IM_COL32(0,255,0,255));
14471
14472 // Combo Box policy (we want a connecting edge)
14473 if (policy == ImGuiPopupPositionPolicy_ComboBox)
14474 {
14475 const ImGuiDir dir_prefered_order[ImGuiDir_COUNT] = {ImGuiDir_Down, ImGuiDir_Right, ImGuiDir_Left, ImGuiDir_Up};
14476 for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_COUNT; n++)
14477 {
14478 const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n];
14479 if (n != -1 && dir == *last_dir) // Already tried this direction?
14480 continue;
14481 ImVec2 pos;
14482 if (dir == ImGuiDir_Down)
14483 pos = ImVec2(r_avoid.Min.x, r_avoid.Max.y); // Below, Toward Right (default)
14484 if (dir == ImGuiDir_Right)
14485 pos = ImVec2(r_avoid.Min.x, r_avoid.Min.y - size.y); // Above, Toward Right
14486 if (dir == ImGuiDir_Left)
14487 pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Max.y); // Below, Toward Left
14488 if (dir == ImGuiDir_Up)
14489 pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Min.y - size.y); // Above, Toward Left
14490 if (!r_outer.Contains(ImRect(pos, pos + size)))
14491 continue;
14492 *last_dir = dir;
14493 return pos;
14494 }
14495 }
14496
14497 // Tooltip and Default popup policy
14498 // (Always first try the direction we used on the last frame, if any)
14499 if (policy == ImGuiPopupPositionPolicy_Tooltip || policy == ImGuiPopupPositionPolicy_Default)
14500 {
14501 const ImGuiDir dir_prefered_order[ImGuiDir_COUNT] = {ImGuiDir_Right, ImGuiDir_Down, ImGuiDir_Up, ImGuiDir_Left};
14502 for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_COUNT; n++)
14503 {
14504 const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n];
14505 if (n != -1 && dir == *last_dir) // Already tried this direction?
14506 continue;
14507
14508 const float avail_w = (dir == ImGuiDir_Left ? r_avoid.Min.x : r_outer.Max.x) -
14509 (dir == ImGuiDir_Right ? r_avoid.Max.x : r_outer.Min.x);
14510 const float avail_h = (dir == ImGuiDir_Up ? r_avoid.Min.y : r_outer.Max.y) -
14511 (dir == ImGuiDir_Down ? r_avoid.Max.y : r_outer.Min.y);
14512
14513 // If there's not enough room on one axis, there's no point in positioning on a side on this axis (e.g. when
14514 // not enough width, use a top/bottom position to maximize available width)
14515 if (avail_w < size.x && (dir == ImGuiDir_Left || dir == ImGuiDir_Right))
14516 continue;
14517 if (avail_h < size.y && (dir == ImGuiDir_Up || dir == ImGuiDir_Down))
14518 continue;
14519
14520 ImVec2 pos;
14521 pos.x = (dir == ImGuiDir_Left) ? r_avoid.Min.x - size.x
14522 : (dir == ImGuiDir_Right) ? r_avoid.Max.x
14523 : base_pos_clamped.x;
14524 pos.y = (dir == ImGuiDir_Up) ? r_avoid.Min.y - size.y
14525 : (dir == ImGuiDir_Down) ? r_avoid.Max.y
14526 : base_pos_clamped.y;
14527
14528 // Clamp top-left corner of popup
14529 pos.x = ImMax(pos.x, r_outer.Min.x);
14530 pos.y = ImMax(pos.y, r_outer.Min.y);
14531
14532 *last_dir = dir;
14533 return pos;
14534 }
14535 }
14536
14537 // Fallback when not enough room:
14538 *last_dir = ImGuiDir_None;
14539
14540 // For tooltip we prefer avoiding the cursor at all cost even if it means that part of the tooltip won't be visible.
14541 if (policy == ImGuiPopupPositionPolicy_Tooltip)
14542 return ref_pos + ImVec2(2, 2);
14543
14544 // Otherwise try to keep within display
14545 ImVec2 pos = ref_pos;
14546 pos.x = ImMax(ImMin(pos.x + size.x, r_outer.Max.x) - size.x, r_outer.Min.x);
14547 pos.y = ImMax(ImMin(pos.y + size.y, r_outer.Max.y) - size.y, r_outer.Min.y);
14548 return pos;
14549}
14550
14551// Note that this is used for popups, which can overlap the non work-area of individual viewports.
14552ImRect ImGui::GetPopupAllowedExtentRect(ImGuiWindow *window)
14553{
14554 ImGuiContext &g = *GImGui;
14555 ImRect r_screen;
14556 if (window->ViewportAllowPlatformMonitorExtend >= 0)
14557 {
14558 // Extent with be in the frame of reference of the given viewport (so Min is likely to be negative here)
14559 const ImGuiPlatformMonitor &monitor = g.PlatformIO.Monitors[window->ViewportAllowPlatformMonitorExtend];
14560 r_screen.Min = monitor.WorkPos;
14561 r_screen.Max = monitor.WorkPos + monitor.WorkSize;
14562 }
14563 else
14564 {
14565 // Use the full viewport area (not work area) for popups
14566 r_screen = window->Viewport->GetMainRect();
14567 }
14568 ImVec2 padding = g.Style.DisplaySafeAreaPadding;
14569 r_screen.Expand(ImVec2((r_screen.GetWidth() > padding.x * 2) ? -padding.x : 0.0f,
14570 (r_screen.GetHeight() > padding.y * 2) ? -padding.y : 0.0f));
14571 return r_screen;
14572}
14573
14574ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow *window)
14575{
14576 ImGuiContext &g = *GImGui;
14577
14578 ImRect r_outer = GetPopupAllowedExtentRect(window);
14579 if (window->Flags & ImGuiWindowFlags_ChildMenu)
14580 {
14581 // Child menus typically request _any_ position within the parent menu item, and then we move the new menu
14582 // outside the parent bounds. This is how we end up with child menus appearing (most-commonly) on the right of
14583 // the parent menu.
14584 ImGuiWindow *parent_window = window->ParentWindow;
14585 float horizontal_overlap =
14586 g.Style.ItemInnerSpacing.x; // We want some overlap to convey the relative depth of each menu (currently the
14587 // amount of overlap is hard-coded to style.ItemSpacing.x).
14588 ImRect r_avoid;
14589 if (parent_window->DC.MenuBarAppending)
14590 r_avoid =
14591 ImRect(-FLT_MAX, parent_window->ClipRect.Min.y, FLT_MAX,
14592 parent_window->ClipRect.Max
14593 .y); // Avoid parent menu-bar. If we wanted multi-line menu-bar, we may instead want to have
14594 // the calling window setup e.g. a NextWindowData.PosConstraintAvoidRect field
14595 else
14596 r_avoid = ImRect(parent_window->Pos.x + horizontal_overlap, -FLT_MAX,
14597 parent_window->Pos.x + parent_window->Size.x - horizontal_overlap -
14598 parent_window->ScrollbarSizes.x,
14599 FLT_MAX);
14600 return FindBestWindowPosForPopupEx(window->Pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid,
14601 ImGuiPopupPositionPolicy_Default);
14602 }
14603 if (window->Flags & ImGuiWindowFlags_Popup)
14604 {
14605 return FindBestWindowPosForPopupEx(window->Pos, window->Size, &window->AutoPosLastDirection, r_outer,
14606 ImRect(window->Pos, window->Pos),
14607 ImGuiPopupPositionPolicy_Default); // Ideally we'd disable r_avoid here
14608 }
14609 if (window->Flags & ImGuiWindowFlags_Tooltip)
14610 {
14611 // Position tooltip (always follows mouse + clamp within outer boundaries)
14612 // FIXME:
14613 // - Too many paths. One problem is that FindBestWindowPosForPopupEx() doesn't allow passing a suggested
14614 // position (so touch screen path doesn't use it by default).
14615 // - Drag and drop tooltips are not using this path either: BeginTooltipEx() manually sets their position.
14616 // - Require some tidying up. In theory we could handle both cases in same location, but requires a bit of
14617 // shuffling
14618 // as drag and drop tooltips are calling SetNextWindowPos() leading to 'window_pos_set_by_api' being set in
14619 // Begin().
14620 IM_ASSERT(g.CurrentWindow == window);
14621 const float scale = g.Style.MouseCursorScale;
14622 const ImVec2 ref_pos = NavCalcPreferredRefPos();
14623
14624 if (g.IO.MouseSource == ImGuiMouseSource_TouchScreen &&
14625 NavCalcPreferredRefPosSource() == ImGuiInputSource_Mouse)
14626 {
14627 ImVec2 tooltip_pos =
14628 ref_pos + TOOLTIP_DEFAULT_OFFSET_TOUCH * scale - (TOOLTIP_DEFAULT_PIVOT_TOUCH * window->Size);
14629 if (r_outer.Contains(ImRect(tooltip_pos, tooltip_pos + window->Size)))
14630 return tooltip_pos;
14631 }
14632
14633 ImVec2 tooltip_pos = ref_pos + TOOLTIP_DEFAULT_OFFSET_MOUSE * scale;
14634 ImRect r_avoid;
14635 if (g.NavCursorVisible && g.NavHighlightItemUnderNav && !g.IO.ConfigNavMoveSetMousePos)
14636 r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 16, ref_pos.y + 8);
14637 else
14638 r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 24 * scale,
14639 ref_pos.y + 24 * scale); // FIXME: Hard-coded based on mouse cursor shape expectation.
14640 // Exact dimension not very important.
14641 // GetForegroundDrawList()->AddRect(r_avoid.Min, r_avoid.Max, IM_COL32(255, 0, 255, 255));
14642
14643 return FindBestWindowPosForPopupEx(tooltip_pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid,
14644 ImGuiPopupPositionPolicy_Tooltip);
14645 }
14646 IM_ASSERT(0);
14647 return window->Pos;
14648}
14649
14650//-----------------------------------------------------------------------------
14651// [SECTION] WINDOW FOCUS
14652//----------------------------------------------------------------------------
14653// - SetWindowFocus()
14654// - SetNextWindowFocus()
14655// - IsWindowFocused()
14656// - UpdateWindowInFocusOrderList() [Internal]
14657// - BringWindowToFocusFront() [Internal]
14658// - BringWindowToDisplayFront() [Internal]
14659// - BringWindowToDisplayBack() [Internal]
14660// - BringWindowToDisplayBehind() [Internal]
14661// - FindWindowDisplayIndex() [Internal]
14662// - FocusWindow() [Internal]
14663// - FocusTopMostWindowUnderOne() [Internal]
14664//-----------------------------------------------------------------------------
14665
14666void ImGui::SetWindowFocus()
14667{
14668 FocusWindow(GImGui->CurrentWindow);
14669}
14670
14671void ImGui::SetWindowFocus(const char *name)
14672{
14673 if (name)
14674 {
14675 if (ImGuiWindow *window = FindWindowByName(name))
14676 FocusWindow(window);
14677 }
14678 else
14679 {
14680 FocusWindow(NULL);
14681 }
14682}
14683
14684void ImGui::SetNextWindowFocus()
14685{
14686 ImGuiContext &g = *GImGui;
14687 g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasFocus;
14688}
14689
14690// Similar to IsWindowHovered()
14691bool ImGui::IsWindowFocused(ImGuiFocusedFlags flags)
14692{
14693 ImGuiContext &g = *GImGui;
14694 ImGuiWindow *ref_window = g.NavWindow;
14695 ImGuiWindow *cur_window = g.CurrentWindow;
14696
14697 if (ref_window == NULL)
14698 return false;
14699 if (flags & ImGuiFocusedFlags_AnyWindow)
14700 return true;
14701
14702 IM_ASSERT(cur_window); // Not inside a Begin()/End()
14703 const bool popup_hierarchy = (flags & ImGuiFocusedFlags_NoPopupHierarchy) == 0;
14704 const bool dock_hierarchy = (flags & ImGuiFocusedFlags_DockHierarchy) != 0;
14705 if (flags & ImGuiHoveredFlags_RootWindow)
14706 cur_window = GetCombinedRootWindow(cur_window, popup_hierarchy, dock_hierarchy);
14707
14708 if (flags & ImGuiHoveredFlags_ChildWindows)
14709 return IsWindowChildOf(ref_window, cur_window, popup_hierarchy, dock_hierarchy);
14710 else
14711 return (ref_window == cur_window);
14712}
14713
14714static int ImGui::FindWindowFocusIndex(ImGuiWindow *window)
14715{
14716 ImGuiContext &g = *GImGui;
14717 IM_UNUSED(g);
14718 int order = window->FocusOrder;
14719 IM_ASSERT(window->RootWindow == window); // No child window (not testing _ChildWindow because of docking)
14720 IM_ASSERT(g.WindowsFocusOrder[order] == window);
14721 return order;
14722}
14723
14724static void ImGui::UpdateWindowInFocusOrderList(ImGuiWindow *window, bool just_created, ImGuiWindowFlags new_flags)
14725{
14726 ImGuiContext &g = *GImGui;
14727
14728 const bool new_is_explicit_child =
14729 (new_flags & ImGuiWindowFlags_ChildWindow) != 0 &&
14730 ((new_flags & ImGuiWindowFlags_Popup) == 0 || (new_flags & ImGuiWindowFlags_ChildMenu) != 0);
14731 const bool child_flag_changed = new_is_explicit_child != window->IsExplicitChild;
14732 if ((just_created || child_flag_changed) && !new_is_explicit_child)
14733 {
14734 IM_ASSERT(!g.WindowsFocusOrder.contains(window));
14735 g.WindowsFocusOrder.push_back(window);
14736 window->FocusOrder = (short)(g.WindowsFocusOrder.Size - 1);
14737 }
14738 else if (!just_created && child_flag_changed && new_is_explicit_child)
14739 {
14740 IM_ASSERT(g.WindowsFocusOrder[window->FocusOrder] == window);
14741 for (int n = window->FocusOrder + 1; n < g.WindowsFocusOrder.Size; n++)
14742 g.WindowsFocusOrder[n]->FocusOrder--;
14743 g.WindowsFocusOrder.erase(g.WindowsFocusOrder.Data + window->FocusOrder);
14744 window->FocusOrder = -1;
14745 }
14746 window->IsExplicitChild = new_is_explicit_child;
14747}
14748
14749void ImGui::BringWindowToFocusFront(ImGuiWindow *window)
14750{
14751 ImGuiContext &g = *GImGui;
14752 IM_ASSERT(window == window->RootWindow);
14753
14754 const int cur_order = window->FocusOrder;
14755 IM_ASSERT(g.WindowsFocusOrder[cur_order] == window);
14756 if (g.WindowsFocusOrder.back() == window)
14757 return;
14758
14759 const int new_order = g.WindowsFocusOrder.Size - 1;
14760 for (int n = cur_order; n < new_order; n++)
14761 {
14762 g.WindowsFocusOrder[n] = g.WindowsFocusOrder[n + 1];
14763 g.WindowsFocusOrder[n]->FocusOrder--;
14764 IM_ASSERT(g.WindowsFocusOrder[n]->FocusOrder == n);
14765 }
14766 g.WindowsFocusOrder[new_order] = window;
14767 window->FocusOrder = (short)new_order;
14768}
14769
14770// Note technically focus related but rather adjacent and close to BringWindowToFocusFront()
14771void ImGui::BringWindowToDisplayFront(ImGuiWindow *window)
14772{
14773 ImGuiContext &g = *GImGui;
14774 ImGuiWindow *current_front_window = g.Windows.back();
14775 if (current_front_window == window ||
14776 current_front_window->RootWindowDockTree == window) // Cheap early out (could be better)
14777 return;
14778 for (int i = g.Windows.Size - 2; i >= 0; i--) // We can ignore the top-most window
14779 if (g.Windows[i] == window)
14780 {
14781 memmove(&g.Windows[i], &g.Windows[i + 1], (size_t)(g.Windows.Size - i - 1) * sizeof(ImGuiWindow *));
14782 g.Windows[g.Windows.Size - 1] = window;
14783 break;
14784 }
14785}
14786
14787void ImGui::BringWindowToDisplayBack(ImGuiWindow *window)
14788{
14789 ImGuiContext &g = *GImGui;
14790 if (g.Windows[0] == window)
14791 return;
14792 for (int i = 0; i < g.Windows.Size; i++)
14793 if (g.Windows[i] == window)
14794 {
14795 memmove(&g.Windows[1], &g.Windows[0], (size_t)i * sizeof(ImGuiWindow *));
14796 g.Windows[0] = window;
14797 break;
14798 }
14799}
14800
14801void ImGui::BringWindowToDisplayBehind(ImGuiWindow *window, ImGuiWindow *behind_window)
14802{
14803 IM_ASSERT(window != NULL && behind_window != NULL);
14804 ImGuiContext &g = *GImGui;
14805 window = window->RootWindow;
14806 behind_window = behind_window->RootWindow;
14807 int pos_wnd = FindWindowDisplayIndex(window);
14808 int pos_beh = FindWindowDisplayIndex(behind_window);
14809 if (pos_wnd < pos_beh)
14810 {
14811 size_t copy_bytes = (pos_beh - pos_wnd - 1) * sizeof(ImGuiWindow *);
14812 memmove(&g.Windows.Data[pos_wnd], &g.Windows.Data[pos_wnd + 1], copy_bytes);
14813 g.Windows[pos_beh - 1] = window;
14814 }
14815 else
14816 {
14817 size_t copy_bytes = (pos_wnd - pos_beh) * sizeof(ImGuiWindow *);
14818 memmove(&g.Windows.Data[pos_beh + 1], &g.Windows.Data[pos_beh], copy_bytes);
14819 g.Windows[pos_beh] = window;
14820 }
14821}
14822
14823int ImGui::FindWindowDisplayIndex(ImGuiWindow *window)
14824{
14825 ImGuiContext &g = *GImGui;
14826 return g.Windows.index_from_ptr(g.Windows.find(window));
14827}
14828
14829// Moving window to front of display and set focus (which happens to be back of our sorted list)
14830void ImGui::FocusWindow(ImGuiWindow *window, ImGuiFocusRequestFlags flags)
14831{
14832 ImGuiContext &g = *GImGui;
14833
14834 // Modal check?
14835 if ((flags & ImGuiFocusRequestFlags_UnlessBelowModal) && (g.NavWindow != window)) // Early out in common case.
14836 if (ImGuiWindow *blocking_modal = FindBlockingModal(window))
14837 {
14838 // This block would typically be reached in two situations:
14839 // - API call to FocusWindow() with a window under a modal and ImGuiFocusRequestFlags_UnlessBelowModal flag.
14840 // - User clicking on void or anything behind a modal while a modal is open (window == NULL)
14841 IMGUI_DEBUG_LOG_FOCUS("[focus] FocusWindow(\"%s\", UnlessBelowModal): prevented by \"%s\".\n",
14842 window ? window->Name : "<NULL>", blocking_modal->Name);
14843 if (window && window == window->RootWindow && (window->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus) == 0)
14844 BringWindowToDisplayBehind(
14845 window, blocking_modal); // Still bring right under modal. (FIXME: Could move in focus list too?)
14846 ClosePopupsOverWindow(GetTopMostPopupModal(), false); // Note how we need to use GetTopMostPopupModal() aad
14847 // NOT blocking_modal, to handle nested modals
14848 return;
14849 }
14850
14851 // Find last focused child (if any) and focus it instead.
14852 if ((flags & ImGuiFocusRequestFlags_RestoreFocusedChild) && window != NULL)
14853 window = NavRestoreLastChildNavWindow(window);
14854
14855 // Apply focus
14856 if (g.NavWindow != window)
14857 {
14858 SetNavWindow(window);
14859 if (window && g.NavHighlightItemUnderNav)
14860 g.NavMousePosDirty = true;
14861 g.NavId = window ? window->NavLastIds[0] : 0; // Restore NavId
14862 g.NavLayer = ImGuiNavLayer_Main;
14863 SetNavFocusScope(window ? window->NavRootFocusScopeId : 0);
14864 g.NavIdIsAlive = false;
14865 g.NavLastValidSelectionUserData = ImGuiSelectionUserData_Invalid;
14866
14867 // Close popups if any
14868 ClosePopupsOverWindow(window, false);
14869 }
14870
14871 // Move the root window to the top of the pile
14872 IM_ASSERT(window == NULL || window->RootWindowDockTree != NULL);
14873 ImGuiWindow *focus_front_window = window ? window->RootWindow : NULL;
14874 ImGuiWindow *display_front_window = window ? window->RootWindowDockTree : NULL;
14875 ImGuiDockNode *dock_node = window ? window->DockNode : NULL;
14876 bool active_id_window_is_dock_node_host =
14877 (g.ActiveIdWindow && dock_node && dock_node->HostWindow == g.ActiveIdWindow);
14878
14879 // Steal active widgets. Some of the cases it triggers includes:
14880 // - Focus a window while an InputText in another window is active, if focus happens before the old InputText can
14881 // run.
14882 // - When using Nav to activate menu items (due to timing of activating on press->new window appears->losing
14883 // ActiveId)
14884 // - Using dock host items (tab, collapse button) can trigger this before we redirect the ActiveIdWindow toward the
14885 // child window.
14886 if (g.ActiveId != 0 && g.ActiveIdWindow && g.ActiveIdWindow->RootWindow != focus_front_window)
14887 if (!g.ActiveIdNoClearOnFocusLoss && !active_id_window_is_dock_node_host)
14888 ClearActiveID();
14889
14890 // Passing NULL allow to disable keyboard focus
14891 if (!window)
14892 return;
14893 window->LastFrameJustFocused = g.FrameCount;
14894
14895 // Select in dock node
14896 // For #2304 we avoid applying focus immediately before the tabbar is visible.
14897 // if (dock_node && dock_node->TabBar)
14898 // dock_node->TabBar->SelectedTabId = dock_node->TabBar->NextSelectedTabId = window->TabId;
14899
14900 // Bring to front
14901 BringWindowToFocusFront(focus_front_window);
14902 if (((window->Flags | focus_front_window->Flags | display_front_window->Flags) &
14903 ImGuiWindowFlags_NoBringToFrontOnFocus) == 0)
14904 BringWindowToDisplayFront(display_front_window);
14905}
14906
14907void ImGui::FocusTopMostWindowUnderOne(ImGuiWindow *under_this_window, ImGuiWindow *ignore_window,
14908 ImGuiViewport *filter_viewport, ImGuiFocusRequestFlags flags)
14909{
14910 ImGuiContext &g = *GImGui;
14911 int start_idx = g.WindowsFocusOrder.Size - 1;
14912 if (under_this_window != NULL)
14913 {
14914 // Aim at root window behind us, if we are in a child window that's our own root (see #4640)
14915 int offset = -1;
14916 while (under_this_window->Flags & ImGuiWindowFlags_ChildWindow)
14917 {
14918 under_this_window = under_this_window->ParentWindow;
14919 offset = 0;
14920 }
14921 start_idx = FindWindowFocusIndex(under_this_window) + offset;
14922 }
14923 for (int i = start_idx; i >= 0; i--)
14924 {
14925 // We may later decide to test for different NoXXXInputs based on the active navigation input (mouse vs nav) but
14926 // that may feel more confusing to the user.
14927 ImGuiWindow *window = g.WindowsFocusOrder[i];
14928 if (window == ignore_window || !window->WasActive)
14929 continue;
14930 if (filter_viewport != NULL && window->Viewport != filter_viewport)
14931 continue;
14932 if ((window->Flags & (ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs)) !=
14933 (ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs))
14934 {
14935 // FIXME-DOCK: When ImGuiFocusRequestFlags_RestoreFocusedChild is set...
14936 // This is failing (lagging by one frame) for docked windows.
14937 // If A and B are docked into window and B disappear, at the NewFrame() call site
14938 // window->NavLastChildNavWindow will still point to B. We might leverage the tab order implicitly stored in
14939 // window->DockNodeAsHost->TabBar (essentially the 'most_recently_selected_tab' code in tab bar will do that
14940 // but on next update) to tell which is the "previous" window. Or we may leverage
14941 // 'LastFrameFocused/LastFrameJustFocused' and have this function handle child window itself?
14942 FocusWindow(window, flags);
14943 return;
14944 }
14945 }
14946 FocusWindow(NULL, flags);
14947}
14948
14949//-----------------------------------------------------------------------------
14950// [SECTION] KEYBOARD/GAMEPAD NAVIGATION
14951//-----------------------------------------------------------------------------
14952
14953// FIXME-NAV: The existence of SetNavID vs SetFocusID vs FocusWindow() needs to be clarified/reworked.
14954// In our terminology those should be interchangeable, yet right now this is super confusing.
14955// Those two functions are merely a legacy artifact, so at minimum naming should be clarified.
14956
14957void ImGui::SetNavCursorVisible(bool visible)
14958{
14959 ImGuiContext &g = *GImGui;
14960 if (g.IO.ConfigNavCursorVisibleAlways)
14961 visible = true;
14962 g.NavCursorVisible = visible;
14963}
14964
14965// (was called NavRestoreHighlightAfterMove() before 1.91.4)
14966void ImGui::SetNavCursorVisibleAfterMove()
14967{
14968 ImGuiContext &g = *GImGui;
14969 if (g.IO.ConfigNavCursorVisibleAuto)
14970 g.NavCursorVisible = true;
14971 g.NavHighlightItemUnderNav = g.NavMousePosDirty = true;
14972}
14973
14974void ImGui::SetNavWindow(ImGuiWindow *window)
14975{
14976 ImGuiContext &g = *GImGui;
14977 if (g.NavWindow != window)
14978 {
14979 IMGUI_DEBUG_LOG_FOCUS("[focus] SetNavWindow(\"%s\")\n", window ? window->Name : "<NULL>");
14980 g.NavWindow = window;
14981 g.NavLastValidSelectionUserData = ImGuiSelectionUserData_Invalid;
14982 }
14983 g.NavInitRequest = g.NavMoveSubmitted = g.NavMoveScoringItems = false;
14984 NavUpdateAnyRequestFlag();
14985}
14986
14987void ImGui::NavHighlightActivated(ImGuiID id)
14988{
14989 ImGuiContext &g = *GImGui;
14990 g.NavHighlightActivatedId = id;
14991 g.NavHighlightActivatedTimer = NAV_ACTIVATE_HIGHLIGHT_TIMER;
14992}
14993
14994void ImGui::NavClearPreferredPosForAxis(ImGuiAxis axis)
14995{
14996 ImGuiContext &g = *GImGui;
14997 g.NavWindow->RootWindowForNav->NavPreferredScoringPosRel[g.NavLayer][axis] = FLT_MAX;
14998}
14999
15000void ImGui::SetNavID(ImGuiID id, ImGuiNavLayer nav_layer, ImGuiID focus_scope_id, const ImRect &rect_rel)
15001{
15002 ImGuiContext &g = *GImGui;
15003 IM_ASSERT(g.NavWindow != NULL);
15004 IM_ASSERT(nav_layer == ImGuiNavLayer_Main || nav_layer == ImGuiNavLayer_Menu);
15005 g.NavId = id;
15006 g.NavLayer = nav_layer;
15007 SetNavFocusScope(focus_scope_id);
15008 g.NavWindow->NavLastIds[nav_layer] = id;
15009 g.NavWindow->NavRectRel[nav_layer] = rect_rel;
15010
15011 // Clear preferred scoring position (NavMoveRequestApplyResult() will tend to restore it)
15012 NavClearPreferredPosForAxis(ImGuiAxis_X);
15013 NavClearPreferredPosForAxis(ImGuiAxis_Y);
15014}
15015
15016void ImGui::SetFocusID(ImGuiID id, ImGuiWindow *window)
15017{
15018 ImGuiContext &g = *GImGui;
15019 IM_ASSERT(id != 0);
15020
15021 if (g.NavWindow != window)
15022 SetNavWindow(window);
15023
15024 // Assume that SetFocusID() is called in the context where its window->DC.NavLayerCurrent and g.CurrentFocusScopeId
15025 // are valid. Note that window may be != g.CurrentWindow (e.g. SetFocusID call in InputTextEx for multi-line text)
15026 const ImGuiNavLayer nav_layer = window->DC.NavLayerCurrent;
15027 g.NavId = id;
15028 g.NavLayer = nav_layer;
15029 SetNavFocusScope(g.CurrentFocusScopeId);
15030 window->NavLastIds[nav_layer] = id;
15031 if (g.LastItemData.ID == id)
15032 window->NavRectRel[nav_layer] = WindowRectAbsToRel(window, g.LastItemData.NavRect);
15033
15034 if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad)
15035 g.NavHighlightItemUnderNav = true;
15036 else if (g.IO.ConfigNavCursorVisibleAuto)
15037 g.NavCursorVisible = false;
15038
15039 // Clear preferred scoring position (NavMoveRequestApplyResult() will tend to restore it)
15040 NavClearPreferredPosForAxis(ImGuiAxis_X);
15041 NavClearPreferredPosForAxis(ImGuiAxis_Y);
15042}
15043
15044static ImGuiDir ImGetDirQuadrantFromDelta(float dx, float dy)
15045{
15046 if (ImFabs(dx) > ImFabs(dy))
15047 return (dx > 0.0f) ? ImGuiDir_Right : ImGuiDir_Left;
15048 return (dy > 0.0f) ? ImGuiDir_Down : ImGuiDir_Up;
15049}
15050
15051static float inline NavScoreItemDistInterval(float cand_min, float cand_max, float curr_min, float curr_max)
15052{
15053 if (cand_max < curr_min)
15054 return cand_max - curr_min;
15055 if (curr_max < cand_min)
15056 return cand_min - curr_max;
15057 return 0.0f;
15058}
15059
15060// Scoring function for keyboard/gamepad directional navigation. Based on https://gist.github.com/rygorous/6981057
15061static bool ImGui::NavScoreItem(ImGuiNavItemData *result)
15062{
15063 ImGuiContext &g = *GImGui;
15064 ImGuiWindow *window = g.CurrentWindow;
15065 if (g.NavLayer != window->DC.NavLayerCurrent)
15066 return false;
15067
15068 // FIXME: Those are not good variables names
15069 ImRect cand = g.LastItemData.NavRect; // Current item nav rectangle
15070 const ImRect curr = g.NavScoringRect; // Current modified source rect (NB: we've applied Max.x = Min.x in
15071 // NavUpdate() to inhibit the effect of having varied item width)
15072 g.NavScoringDebugCount++;
15073
15074 // When entering through a NavFlattened border, we consider child window items as fully clipped for scoring
15075 if (window->ParentWindow == g.NavWindow)
15076 {
15077 IM_ASSERT((window->ChildFlags | g.NavWindow->ChildFlags) & ImGuiChildFlags_NavFlattened);
15078 if (!window->ClipRect.Overlaps(cand))
15079 return false;
15080 cand.ClipWithFull(
15081 window->ClipRect); // This allows the scored item to not overlap other candidates in the parent window
15082 }
15083
15084 // Compute distance between boxes
15085 // FIXME-NAV: Introducing biases for vertical navigation, needs to be removed.
15086 float dbx = NavScoreItemDistInterval(cand.Min.x, cand.Max.x, curr.Min.x, curr.Max.x);
15087 float dby = NavScoreItemDistInterval(
15088 ImLerp(cand.Min.y, cand.Max.y, 0.2f), ImLerp(cand.Min.y, cand.Max.y, 0.8f),
15089 ImLerp(curr.Min.y, curr.Max.y, 0.2f),
15090 ImLerp(curr.Min.y, curr.Max.y,
15091 0.8f)); // Scale down on Y to keep using box-distance for vertically touching items
15092 if (dby != 0.0f && dbx != 0.0f)
15093 dbx = (dbx / 1000.0f) + ((dbx > 0.0f) ? +1.0f : -1.0f);
15094 float dist_box = ImFabs(dbx) + ImFabs(dby);
15095
15096 // Compute distance between centers (this is off by a factor of 2, but we only compare center distances with each
15097 // other so it doesn't matter)
15098 float dcx = (cand.Min.x + cand.Max.x) - (curr.Min.x + curr.Max.x);
15099 float dcy = (cand.Min.y + cand.Max.y) - (curr.Min.y + curr.Max.y);
15100 float dist_center = ImFabs(dcx) + ImFabs(dcy); // L1 metric (need this for our connectedness guarantee)
15101
15102 // Determine which quadrant of 'curr' our candidate item 'cand' lies in based on distance
15103 ImGuiDir quadrant;
15104 float dax = 0.0f, day = 0.0f, dist_axial = 0.0f;
15105 if (dbx != 0.0f || dby != 0.0f)
15106 {
15107 // For non-overlapping boxes, use distance between boxes
15108 // FIXME-NAV: Quadrant may be incorrect because of (1) dbx bias and (2) curr.Max.y bias applied by
15109 // NavBiasScoringRect() where typically curr.Max.y==curr.Min.y One typical case where this happens, with
15110 // style.WindowMenuButtonPosition == ImGuiDir_Right, pressing Left to navigate from Close to Collapse tends to
15111 // fail. Also see #6344. Calling ImGetDirQuadrantFromDelta() with unbiased values may be good but side-effects
15112 // are plenty.
15113 dax = dbx;
15114 day = dby;
15115 dist_axial = dist_box;
15116 quadrant = ImGetDirQuadrantFromDelta(dbx, dby);
15117 }
15118 else if (dcx != 0.0f || dcy != 0.0f)
15119 {
15120 // For overlapping boxes with different centers, use distance between centers
15121 dax = dcx;
15122 day = dcy;
15123 dist_axial = dist_center;
15124 quadrant = ImGetDirQuadrantFromDelta(dcx, dcy);
15125 }
15126 else
15127 {
15128 // Degenerate case: two overlapping buttons with same center, break ties arbitrarily (note that LastItemId here
15129 // is really the _previous_ item order, but it doesn't matter)
15130 quadrant = (g.LastItemData.ID < g.NavId) ? ImGuiDir_Left : ImGuiDir_Right;
15131 }
15132
15133 const ImGuiDir move_dir = g.NavMoveDir;
15134#if IMGUI_DEBUG_NAV_SCORING
15135 char buf[200];
15136 if (g.IO.KeyCtrl) // Hold CTRL to preview score in matching quadrant. CTRL+Arrow to rotate.
15137 {
15138 if (quadrant == move_dir)
15139 {
15140 ImFormatString(buf, IM_ARRAYSIZE(buf), "%.0f/%.0f", dist_box, dist_center);
15141 ImDrawList *draw_list = GetForegroundDrawList(window);
15142 draw_list->AddRectFilled(cand.Min, cand.Max, IM_COL32(255, 0, 0, 80));
15143 draw_list->AddRectFilled(cand.Min, cand.Min + CalcTextSize(buf), IM_COL32(255, 0, 0, 200));
15144 draw_list->AddText(cand.Min, IM_COL32(255, 255, 255, 255), buf);
15145 }
15146 }
15147 const bool debug_hovering = IsMouseHoveringRect(cand.Min, cand.Max);
15148 const bool debug_tty = (g.IO.KeyCtrl && IsKeyPressed(ImGuiKey_Space));
15149 if (debug_hovering || debug_tty)
15150 {
15151 ImFormatString(buf, IM_ARRAYSIZE(buf),
15152 "d-box (%7.3f,%7.3f) -> %7.3f\nd-center (%7.3f,%7.3f) -> %7.3f\nd-axial (%7.3f,%7.3f) -> "
15153 "%7.3f\nnav %c, quadrant %c",
15154 dbx, dby, dist_box, dcx, dcy, dist_center, dax, day, dist_axial, "-WENS"[move_dir + 1],
15155 "-WENS"[quadrant + 1]);
15156 if (debug_hovering)
15157 {
15158 ImDrawList *draw_list = GetForegroundDrawList(window);
15159 draw_list->AddRect(curr.Min, curr.Max, IM_COL32(255, 200, 0, 100));
15160 draw_list->AddRect(cand.Min, cand.Max, IM_COL32(255, 255, 0, 200));
15161 draw_list->AddRectFilled(cand.Max - ImVec2(4, 4), cand.Max + CalcTextSize(buf) + ImVec2(4, 4),
15162 IM_COL32(40, 0, 0, 200));
15163 draw_list->AddText(cand.Max, ~0U, buf);
15164 }
15165 if (debug_tty)
15166 {
15167 IMGUI_DEBUG_LOG_NAV("id 0x%08X\n%s\n", g.LastItemData.ID, buf);
15168 }
15169 }
15170#endif
15171
15172 // Is it in the quadrant we're interested in moving to?
15173 bool new_best = false;
15174 if (quadrant == move_dir)
15175 {
15176 // Does it beat the current best candidate?
15177 if (dist_box < result->DistBox)
15178 {
15179 result->DistBox = dist_box;
15180 result->DistCenter = dist_center;
15181 return true;
15182 }
15183 if (dist_box == result->DistBox)
15184 {
15185 // Try using distance between center points to break ties
15186 if (dist_center < result->DistCenter)
15187 {
15188 result->DistCenter = dist_center;
15189 new_best = true;
15190 }
15191 else if (dist_center == result->DistCenter)
15192 {
15193 // Still tied! we need to be extra-careful to make sure everything gets linked properly. We consistently
15194 // break ties by symbolically moving "later" items (with higher index) to the right/downwards by an
15195 // infinitesimal amount since we the current "best" button already (so it must have a lower index), this
15196 // is fairly easy. This rule ensures that all buttons with dx==dy==0 will end up being linked in order
15197 // of appearance along the x axis.
15198 if (((move_dir == ImGuiDir_Up || move_dir == ImGuiDir_Down) ? dby : dbx) <
15199 0.0f) // moving bj to the right/down decreases distance
15200 new_best = true;
15201 }
15202 }
15203 }
15204
15205 // Axial check: if 'curr' has no link at all in some direction and 'cand' lies roughly in that direction, add a
15206 // tentative link. This will only be kept if no "real" matches are found, so it only augments the graph produced by
15207 // the above method using extra links. (important, since it doesn't guarantee strong connectedness) This is just to
15208 // avoid buttons having no links in a particular direction when there's a suitable neighbor. you get good graphs
15209 // without this too. 2017/09/29: FIXME: This now currently only enabled inside menu bars, ideally we'd disable it
15210 // everywhere. Menus in particular need to catch failure. For general navigation it feels awkward. Disabling it may
15211 // lead to disconnected graphs when nodes are very spaced out on different axis. Perhaps consider offering this as
15212 // an option?
15213 if (result->DistBox == FLT_MAX && dist_axial < result->DistAxial) // Check axial match
15214 if (g.NavLayer == ImGuiNavLayer_Menu && !(g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))
15215 if ((move_dir == ImGuiDir_Left && dax < 0.0f) || (move_dir == ImGuiDir_Right && dax > 0.0f) ||
15216 (move_dir == ImGuiDir_Up && day < 0.0f) || (move_dir == ImGuiDir_Down && day > 0.0f))
15217 {
15218 result->DistAxial = dist_axial;
15219 new_best = true;
15220 }
15221
15222 return new_best;
15223}
15224
15225static void ImGui::NavApplyItemToResult(ImGuiNavItemData *result)
15226{
15227 ImGuiContext &g = *GImGui;
15228 ImGuiWindow *window = g.CurrentWindow;
15229 result->Window = window;
15230 result->ID = g.LastItemData.ID;
15231 result->FocusScopeId = g.CurrentFocusScopeId;
15232 result->ItemFlags = g.LastItemData.ItemFlags;
15233 result->RectRel = WindowRectAbsToRel(window, g.LastItemData.NavRect);
15234 if (result->ItemFlags & ImGuiItemFlags_HasSelectionUserData)
15235 {
15236 IM_ASSERT(g.NextItemData.SelectionUserData != ImGuiSelectionUserData_Invalid);
15237 result->SelectionUserData =
15238 g.NextItemData.SelectionUserData; // INTENTIONAL: At this point this field is not cleared in NextItemData.
15239 // Avoid unnecessary copy to LastItemData.
15240 }
15241}
15242
15243// True when current work location may be scrolled horizontally when moving left / right.
15244// This is generally always true UNLESS within a column. We don't have a vertical equivalent.
15245void ImGui::NavUpdateCurrentWindowIsScrollPushableX()
15246{
15247 ImGuiContext &g = *GImGui;
15248 ImGuiWindow *window = g.CurrentWindow;
15249 window->DC.NavIsScrollPushableX = (g.CurrentTable == NULL && window->DC.CurrentColumns == NULL);
15250}
15251
15252// We get there when either NavId == id, or when g.NavAnyRequest is set (which is updated by NavUpdateAnyRequestFlag
15253// above) This is called after LastItemData is set, but NextItemData is also still valid.
15254static void ImGui::NavProcessItem()
15255{
15256 ImGuiContext &g = *GImGui;
15257 ImGuiWindow *window = g.CurrentWindow;
15258 const ImGuiID id = g.LastItemData.ID;
15259 const ImGuiItemFlags item_flags = g.LastItemData.ItemFlags;
15260
15261 // When inside a container that isn't scrollable with Left<>Right, clip NavRect accordingly (#2221)
15262 if (window->DC.NavIsScrollPushableX == false)
15263 {
15264 g.LastItemData.NavRect.Min.x =
15265 ImClamp(g.LastItemData.NavRect.Min.x, window->ClipRect.Min.x, window->ClipRect.Max.x);
15266 g.LastItemData.NavRect.Max.x =
15267 ImClamp(g.LastItemData.NavRect.Max.x, window->ClipRect.Min.x, window->ClipRect.Max.x);
15268 }
15269 const ImRect nav_bb = g.LastItemData.NavRect;
15270
15271 // Process Init Request
15272 if (g.NavInitRequest && g.NavLayer == window->DC.NavLayerCurrent && (item_flags & ImGuiItemFlags_Disabled) == 0)
15273 {
15274 // Even if 'ImGuiItemFlags_NoNavDefaultFocus' is on (typically collapse/close button) we record the first
15275 // ResultId so they can be used as a fallback
15276 const bool candidate_for_nav_default_focus = (item_flags & ImGuiItemFlags_NoNavDefaultFocus) == 0;
15277 if (candidate_for_nav_default_focus || g.NavInitResult.ID == 0)
15278 {
15279 NavApplyItemToResult(&g.NavInitResult);
15280 }
15281 if (candidate_for_nav_default_focus)
15282 {
15283 g.NavInitRequest = false; // Found a match, clear request
15284 NavUpdateAnyRequestFlag();
15285 }
15286 }
15287
15288 // Process Move Request (scoring for navigation)
15289 // FIXME-NAV: Consider policy for double scoring (scoring from NavScoringRect + scoring from a rect wrapped
15290 // according to current wrapping policy)
15291 if (g.NavMoveScoringItems && (item_flags & ImGuiItemFlags_Disabled) == 0)
15292 {
15293 if ((g.NavMoveFlags & ImGuiNavMoveFlags_FocusApi) || (window->Flags & ImGuiWindowFlags_NoNavInputs) == 0)
15294 {
15295 const bool is_tabbing = (g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) != 0;
15296 if (is_tabbing)
15297 {
15298 NavProcessItemForTabbingRequest(id, item_flags, g.NavMoveFlags);
15299 }
15300 else if (g.NavId != id || (g.NavMoveFlags & ImGuiNavMoveFlags_AllowCurrentNavId))
15301 {
15302 ImGuiNavItemData *result = (window == g.NavWindow) ? &g.NavMoveResultLocal : &g.NavMoveResultOther;
15303 if (NavScoreItem(result))
15304 NavApplyItemToResult(result);
15305
15306 // Features like PageUp/PageDown need to maintain a separate score for the visible set of items.
15307 const float VISIBLE_RATIO = 0.70f;
15308 if ((g.NavMoveFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet) && window->ClipRect.Overlaps(nav_bb))
15309 if (ImClamp(nav_bb.Max.y, window->ClipRect.Min.y, window->ClipRect.Max.y) -
15310 ImClamp(nav_bb.Min.y, window->ClipRect.Min.y, window->ClipRect.Max.y) >=
15311 (nav_bb.Max.y - nav_bb.Min.y) * VISIBLE_RATIO)
15312 if (NavScoreItem(&g.NavMoveResultLocalVisible))
15313 NavApplyItemToResult(&g.NavMoveResultLocalVisible);
15314 }
15315 }
15316 }
15317
15318 // Update information for currently focused/navigated item
15319 if (g.NavId == id)
15320 {
15321 if (g.NavWindow != window)
15322 SetNavWindow(window); // Always refresh g.NavWindow, because some operations such as FocusItem() may not
15323 // have a window.
15324 g.NavLayer = window->DC.NavLayerCurrent;
15325 SetNavFocusScope(g.CurrentFocusScopeId); // Will set g.NavFocusScopeId AND store g.NavFocusScopePath
15326 g.NavFocusScopeId = g.CurrentFocusScopeId;
15327 g.NavIdIsAlive = true;
15328 if (g.LastItemData.ItemFlags & ImGuiItemFlags_HasSelectionUserData)
15329 {
15330 IM_ASSERT(g.NextItemData.SelectionUserData != ImGuiSelectionUserData_Invalid);
15331 g.NavLastValidSelectionUserData =
15332 g.NextItemData.SelectionUserData; // INTENTIONAL: At this point this field is not cleared in
15333 // NextItemData. Avoid unnecessary copy to LastItemData.
15334 }
15335 window->NavRectRel[window->DC.NavLayerCurrent] =
15336 WindowRectAbsToRel(window, nav_bb); // Store item bounding box (relative to window position)
15337 }
15338}
15339
15340// Handle "scoring" of an item for a tabbing/focusing request initiated by NavUpdateCreateTabbingRequest().
15341// Note that SetKeyboardFocusHere() API calls are considered tabbing requests!
15342// - Case 1: no nav/active id: set result to first eligible item, stop storing.
15343// - Case 2: tab forward: on ref id set counter, on counter elapse store result
15344// - Case 3: tab forward wrap: set result to first eligible item (preemptively), on ref id set counter, on next frame
15345// if counter hasn't elapsed store result. // FIXME-TABBING: Could be done as a next-frame forwarded request
15346// - Case 4: tab backward: store all results, on ref id pick prev, stop storing
15347// - Case 5: tab backward wrap: store all results, on ref id if no result keep storing until last // FIXME-TABBING:
15348// Could be done as next-frame forwarded requested
15349void ImGui::NavProcessItemForTabbingRequest(ImGuiID id, ImGuiItemFlags item_flags, ImGuiNavMoveFlags move_flags)
15350{
15351 ImGuiContext &g = *GImGui;
15352
15353 if ((move_flags & ImGuiNavMoveFlags_FocusApi) == 0)
15354 {
15355 if (g.NavLayer != g.CurrentWindow->DC.NavLayerCurrent)
15356 return;
15357 if (g.NavFocusScopeId != g.CurrentFocusScopeId)
15358 return;
15359 }
15360
15361 // - Can always land on an item when using API call.
15362 // - Tabbing with _NavEnableKeyboard (space/enter/arrows): goes through every item.
15363 // - Tabbing without _NavEnableKeyboard: goes through inputable items only.
15364 bool can_stop;
15365 if (move_flags & ImGuiNavMoveFlags_FocusApi)
15366 can_stop = true;
15367 else
15368 can_stop = (item_flags & ImGuiItemFlags_NoTabStop) == 0 &&
15369 ((g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) || (item_flags & ImGuiItemFlags_Inputable));
15370
15371 // Always store in NavMoveResultLocal (unlike directional request which uses NavMoveResultOther on sibling/flattened
15372 // windows)
15373 ImGuiNavItemData *result = &g.NavMoveResultLocal;
15374 if (g.NavTabbingDir == +1)
15375 {
15376 // Tab Forward or SetKeyboardFocusHere() with >= 0
15377 if (can_stop && g.NavTabbingResultFirst.ID == 0)
15378 NavApplyItemToResult(&g.NavTabbingResultFirst);
15379 if (can_stop && g.NavTabbingCounter > 0 && --g.NavTabbingCounter == 0)
15380 NavMoveRequestResolveWithLastItem(result);
15381 else if (g.NavId == id)
15382 g.NavTabbingCounter = 1;
15383 }
15384 else if (g.NavTabbingDir == -1)
15385 {
15386 // Tab Backward
15387 if (g.NavId == id)
15388 {
15389 if (result->ID)
15390 {
15391 g.NavMoveScoringItems = false;
15392 NavUpdateAnyRequestFlag();
15393 }
15394 }
15395 else if (can_stop)
15396 {
15397 // Keep applying until reaching NavId
15398 NavApplyItemToResult(result);
15399 }
15400 }
15401 else if (g.NavTabbingDir == 0)
15402 {
15403 if (can_stop && g.NavId == id)
15404 NavMoveRequestResolveWithLastItem(result);
15405 if (can_stop && g.NavTabbingResultFirst.ID == 0) // Tab init
15406 NavApplyItemToResult(&g.NavTabbingResultFirst);
15407 }
15408}
15409
15410bool ImGui::NavMoveRequestButNoResultYet()
15411{
15412 ImGuiContext &g = *GImGui;
15413 return g.NavMoveScoringItems && g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0;
15414}
15415
15416// FIXME: ScoringRect is not set
15417void ImGui::NavMoveRequestSubmit(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNavMoveFlags move_flags,
15418 ImGuiScrollFlags scroll_flags)
15419{
15420 ImGuiContext &g = *GImGui;
15421 IM_ASSERT(g.NavWindow != NULL);
15422 // IMGUI_DEBUG_LOG_NAV("[nav] NavMoveRequestSubmit: dir %c, window \"%s\"\n", "-WENS"[move_dir + 1],
15423 // g.NavWindow->Name);
15424
15425 if (move_flags & ImGuiNavMoveFlags_IsTabbing)
15426 move_flags |= ImGuiNavMoveFlags_AllowCurrentNavId;
15427
15428 g.NavMoveSubmitted = g.NavMoveScoringItems = true;
15429 g.NavMoveDir = move_dir;
15430 g.NavMoveDirForDebug = move_dir;
15431 g.NavMoveClipDir = clip_dir;
15432 g.NavMoveFlags = move_flags;
15433 g.NavMoveScrollFlags = scroll_flags;
15434 g.NavMoveForwardToNextFrame = false;
15435 g.NavMoveKeyMods = (move_flags & ImGuiNavMoveFlags_FocusApi) ? 0 : g.IO.KeyMods;
15436 g.NavMoveResultLocal.Clear();
15437 g.NavMoveResultLocalVisible.Clear();
15438 g.NavMoveResultOther.Clear();
15439 g.NavTabbingCounter = 0;
15440 g.NavTabbingResultFirst.Clear();
15441 NavUpdateAnyRequestFlag();
15442}
15443
15444void ImGui::NavMoveRequestResolveWithLastItem(ImGuiNavItemData *result)
15445{
15446 ImGuiContext &g = *GImGui;
15447 g.NavMoveScoringItems = false; // Ensure request doesn't need more processing
15448 NavApplyItemToResult(result);
15449 NavUpdateAnyRequestFlag();
15450}
15451
15452// Called by TreePop() to implement ImGuiTreeNodeFlags_NavLeftJumpsBackHere
15453void ImGui::NavMoveRequestResolveWithPastTreeNode(ImGuiNavItemData *result, ImGuiTreeNodeStackData *tree_node_data)
15454{
15455 ImGuiContext &g = *GImGui;
15456 g.NavMoveScoringItems = false;
15457 g.LastItemData.ID = tree_node_data->ID;
15458 g.LastItemData.ItemFlags =
15459 tree_node_data->ItemFlags &
15460 ~ImGuiItemFlags_HasSelectionUserData; // Losing SelectionUserData, recovered next-frame (cheaper).
15461 g.LastItemData.NavRect = tree_node_data->NavRect;
15462 NavApplyItemToResult(result); // Result this instead of implementing a NavApplyPastTreeNodeToResult()
15463 NavClearPreferredPosForAxis(ImGuiAxis_Y);
15464 NavUpdateAnyRequestFlag();
15465}
15466
15467void ImGui::NavMoveRequestCancel()
15468{
15469 ImGuiContext &g = *GImGui;
15470 g.NavMoveSubmitted = g.NavMoveScoringItems = false;
15471 NavUpdateAnyRequestFlag();
15472}
15473
15474// Forward will reuse the move request again on the next frame (generally with modifications done to it)
15475void ImGui::NavMoveRequestForward(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNavMoveFlags move_flags,
15476 ImGuiScrollFlags scroll_flags)
15477{
15478 ImGuiContext &g = *GImGui;
15479 IM_ASSERT(g.NavMoveForwardToNextFrame == false);
15480 NavMoveRequestCancel();
15481 g.NavMoveForwardToNextFrame = true;
15482 g.NavMoveDir = move_dir;
15483 g.NavMoveClipDir = clip_dir;
15484 g.NavMoveFlags = move_flags | ImGuiNavMoveFlags_Forwarded;
15485 g.NavMoveScrollFlags = scroll_flags;
15486}
15487
15488// Navigation wrap-around logic is delayed to the end of the frame because this operation is only valid after entire
15489// popup is assembled and in case of appended popups it is not clear which EndPopup() call is final.
15490void ImGui::NavMoveRequestTryWrapping(ImGuiWindow *window, ImGuiNavMoveFlags wrap_flags)
15491{
15492 ImGuiContext &g = *GImGui;
15493 IM_ASSERT((wrap_flags & ImGuiNavMoveFlags_WrapMask_) != 0 &&
15494 (wrap_flags & ~ImGuiNavMoveFlags_WrapMask_) == 0); // Call with _WrapX, _WrapY, _LoopX, _LoopY
15495
15496 // In theory we should test for NavMoveRequestButNoResultYet() but there's no point doing it:
15497 // as NavEndFrame() will do the same test. It will end up calling NavUpdateCreateWrappingRequest().
15498 if (g.NavWindow == window && g.NavMoveScoringItems && g.NavLayer == ImGuiNavLayer_Main)
15499 g.NavMoveFlags = (g.NavMoveFlags & ~ImGuiNavMoveFlags_WrapMask_) | wrap_flags;
15500}
15501
15502// FIXME: This could be replaced by updating a frame number in each window when (window == NavWindow) and (NavLayer ==
15503// 0). This way we could find the last focused window among our children. It would be much less confusing this way?
15504static void ImGui::NavSaveLastChildNavWindowIntoParent(ImGuiWindow *nav_window)
15505{
15506 ImGuiWindow *parent = nav_window;
15507 while (parent && parent->RootWindow != parent &&
15508 (parent->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0)
15509 parent = parent->ParentWindow;
15510 if (parent && parent != nav_window)
15511 parent->NavLastChildNavWindow = nav_window;
15512}
15513
15514// Restore the last focused child.
15515// Call when we are expected to land on the Main Layer (0) after FocusWindow()
15516static ImGuiWindow *ImGui::NavRestoreLastChildNavWindow(ImGuiWindow *window)
15517{
15518 if (window->NavLastChildNavWindow && window->NavLastChildNavWindow->WasActive)
15519 return window->NavLastChildNavWindow;
15520 if (window->DockNodeAsHost && window->DockNodeAsHost->TabBar)
15521 if (ImGuiTabItem *tab = TabBarFindMostRecentlySelectedTabForActiveWindow(window->DockNodeAsHost->TabBar))
15522 return tab->Window;
15523 return window;
15524}
15525
15526void ImGui::NavRestoreLayer(ImGuiNavLayer layer)
15527{
15528 ImGuiContext &g = *GImGui;
15529 if (layer == ImGuiNavLayer_Main)
15530 {
15531 ImGuiWindow *prev_nav_window = g.NavWindow;
15532 g.NavWindow = NavRestoreLastChildNavWindow(g.NavWindow); // FIXME-NAV: Should clear ongoing nav requests?
15533 g.NavLastValidSelectionUserData = ImGuiSelectionUserData_Invalid;
15534 if (prev_nav_window)
15535 IMGUI_DEBUG_LOG_FOCUS("[focus] NavRestoreLayer: from \"%s\" to SetNavWindow(\"%s\")\n",
15536 prev_nav_window->Name, g.NavWindow->Name);
15537 }
15538 ImGuiWindow *window = g.NavWindow;
15539 if (window->NavLastIds[layer] != 0)
15540 {
15541 SetNavID(window->NavLastIds[layer], layer, 0, window->NavRectRel[layer]);
15542 }
15543 else
15544 {
15545 g.NavLayer = layer;
15546 NavInitWindow(window, true);
15547 }
15548}
15549
15550static inline void ImGui::NavUpdateAnyRequestFlag()
15551{
15552 ImGuiContext &g = *GImGui;
15553 g.NavAnyRequest = g.NavMoveScoringItems || g.NavInitRequest || (IMGUI_DEBUG_NAV_SCORING && g.NavWindow != NULL);
15554 if (g.NavAnyRequest)
15555 IM_ASSERT(g.NavWindow != NULL);
15556}
15557
15558// This needs to be called before we submit any widget (aka in or before Begin)
15559void ImGui::NavInitWindow(ImGuiWindow *window, bool force_reinit)
15560{
15561 // FIXME: ChildWindow test here is wrong for docking
15562 ImGuiContext &g = *GImGui;
15563 IM_ASSERT(window == g.NavWindow);
15564
15565 if (window->Flags & ImGuiWindowFlags_NoNavInputs)
15566 {
15567 g.NavId = 0;
15568 SetNavFocusScope(window->NavRootFocusScopeId);
15569 return;
15570 }
15571
15572 bool init_for_nav = false;
15573 if (window == window->RootWindow || (window->Flags & ImGuiWindowFlags_Popup) || (window->NavLastIds[0] == 0) ||
15574 force_reinit)
15575 init_for_nav = true;
15576 IMGUI_DEBUG_LOG_NAV("[nav] NavInitRequest: from NavInitWindow(), init_for_nav=%d, window=\"%s\", layer=%d\n",
15577 init_for_nav, window->Name, g.NavLayer);
15578 if (init_for_nav)
15579 {
15580 SetNavID(0, g.NavLayer, window->NavRootFocusScopeId, ImRect());
15581 g.NavInitRequest = true;
15582 g.NavInitRequestFromMove = false;
15583 g.NavInitResult.ID = 0;
15584 NavUpdateAnyRequestFlag();
15585 }
15586 else
15587 {
15588 g.NavId = window->NavLastIds[0];
15589 SetNavFocusScope(window->NavRootFocusScopeId);
15590 }
15591}
15592
15593static ImGuiInputSource ImGui::NavCalcPreferredRefPosSource()
15594{
15595 ImGuiContext &g = *GImGui;
15596 ImGuiWindow *window = g.NavWindow;
15597 const bool activated_shortcut = g.ActiveId != 0 && g.ActiveIdFromShortcut && g.ActiveId == g.LastItemData.ID;
15598
15599 // Testing for !activated_shortcut here could in theory be removed if we decided that activating a remote shortcut
15600 // altered one of the g.NavDisableXXX flag.
15601 if ((!g.NavCursorVisible || !g.NavHighlightItemUnderNav || !window) && !activated_shortcut)
15602 return ImGuiInputSource_Mouse;
15603 else
15604 return ImGuiInputSource_Keyboard; // or Nav in general
15605}
15606
15607static ImVec2 ImGui::NavCalcPreferredRefPos()
15608{
15609 ImGuiContext &g = *GImGui;
15610 ImGuiWindow *window = g.NavWindow;
15611 ImGuiInputSource source = NavCalcPreferredRefPosSource();
15612
15613 const bool activated_shortcut = g.ActiveId != 0 && g.ActiveIdFromShortcut && g.ActiveId == g.LastItemData.ID;
15614
15615 // Testing for !activated_shortcut here could in theory be removed if we decided that activating a remote shortcut
15616 // altered one of the g.NavDisableXXX flag.
15617 if (source == ImGuiInputSource_Mouse)
15618 {
15619 // Mouse (we need a fallback in case the mouse becomes invalid after being used)
15620 // The +1.0f offset when stored by OpenPopupEx() allows reopening this or another popup (same or another mouse
15621 // button) while not moving the mouse, it is pretty standard. In theory we could move that +1.0f offset in
15622 // OpenPopupEx()
15623 ImVec2 p = IsMousePosValid(&g.IO.MousePos) ? g.IO.MousePos : g.MouseLastValidPos;
15624 return ImVec2(p.x + 1.0f, p.y);
15625 }
15626 else
15627 {
15628 // When navigation is active and mouse is disabled, pick a position around the bottom left of the currently
15629 // navigated item
15630 ImRect ref_rect;
15631 if (activated_shortcut)
15632 ref_rect = g.LastItemData.NavRect;
15633 else
15634 ref_rect = WindowRectRelToAbs(window, window->NavRectRel[g.NavLayer]);
15635
15636 // Take account of upcoming scrolling (maybe set mouse pos should be done in EndFrame?)
15637 if (window->LastFrameActive != g.FrameCount &&
15638 (window->ScrollTarget.x != FLT_MAX || window->ScrollTarget.y != FLT_MAX))
15639 {
15640 ImVec2 next_scroll = CalcNextScrollFromScrollTargetAndClamp(window);
15641 ref_rect.Translate(window->Scroll - next_scroll);
15642 }
15643 ImVec2 pos = ImVec2(ref_rect.Min.x + ImMin(g.Style.FramePadding.x * 4, ref_rect.GetWidth()),
15644 ref_rect.Max.y - ImMin(g.Style.FramePadding.y, ref_rect.GetHeight()));
15645 ImGuiViewport *viewport = window->Viewport;
15646 return ImTrunc(ImClamp(
15647 pos, viewport->Pos,
15648 viewport->Pos + viewport->Size)); // ImTrunc() is important because non-integer mouse position application
15649 // in backend might be lossy and result in undesirable non-zero delta.
15650 }
15651}
15652
15653float ImGui::GetNavTweakPressedAmount(ImGuiAxis axis)
15654{
15655 ImGuiContext &g = *GImGui;
15656 float repeat_delay, repeat_rate;
15657 GetTypematicRepeatRate(ImGuiInputFlags_RepeatRateNavTweak, &repeat_delay, &repeat_rate);
15658
15659 ImGuiKey key_less, key_more;
15660 if (g.NavInputSource == ImGuiInputSource_Gamepad)
15661 {
15662 key_less = (axis == ImGuiAxis_X) ? ImGuiKey_GamepadDpadLeft : ImGuiKey_GamepadDpadUp;
15663 key_more = (axis == ImGuiAxis_X) ? ImGuiKey_GamepadDpadRight : ImGuiKey_GamepadDpadDown;
15664 }
15665 else
15666 {
15667 key_less = (axis == ImGuiAxis_X) ? ImGuiKey_LeftArrow : ImGuiKey_UpArrow;
15668 key_more = (axis == ImGuiAxis_X) ? ImGuiKey_RightArrow : ImGuiKey_DownArrow;
15669 }
15670 float amount = (float)GetKeyPressedAmount(key_more, repeat_delay, repeat_rate) -
15671 (float)GetKeyPressedAmount(key_less, repeat_delay, repeat_rate);
15672 if (amount != 0.0f && IsKeyDown(key_less) &&
15673 IsKeyDown(key_more)) // Cancel when opposite directions are held, regardless of repeat phase
15674 amount = 0.0f;
15675 return amount;
15676}
15677
15678static void ImGui::NavUpdate()
15679{
15680 ImGuiContext &g = *GImGui;
15681 ImGuiIO &io = g.IO;
15682
15683 io.WantSetMousePos = false;
15684 // if (g.NavScoringDebugCount > 0) IMGUI_DEBUG_LOG_NAV("[nav] NavScoringDebugCount %d for '%s' layer %d (Init:%d,
15685 // Move:%d)\n", g.NavScoringDebugCount, g.NavWindow ? g.NavWindow->Name : "NULL", g.NavLayer, g.NavInitRequest ||
15686 // g.NavInitResultId != 0, g.NavMoveRequest);
15687
15688 // Set input source based on which keys are last pressed (as some features differs when used with Gamepad vs
15689 // Keyboard)
15690 // FIXME-NAV: Now that keys are separated maybe we can get rid of NavInputSource?
15691 const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 &&
15692 (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
15693 const ImGuiKey nav_gamepad_keys_to_change_source[] = {
15694 ImGuiKey_GamepadFaceRight, ImGuiKey_GamepadFaceLeft, ImGuiKey_GamepadFaceUp, ImGuiKey_GamepadFaceDown,
15695 ImGuiKey_GamepadDpadRight, ImGuiKey_GamepadDpadLeft, ImGuiKey_GamepadDpadUp, ImGuiKey_GamepadDpadDown};
15696 if (nav_gamepad_active)
15697 for (ImGuiKey key : nav_gamepad_keys_to_change_source)
15698 if (IsKeyDown(key))
15699 g.NavInputSource = ImGuiInputSource_Gamepad;
15700 const bool nav_keyboard_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0;
15701 const ImGuiKey nav_keyboard_keys_to_change_source[] = {ImGuiKey_Space, ImGuiKey_Enter, ImGuiKey_Escape,
15702 ImGuiKey_RightArrow, ImGuiKey_LeftArrow, ImGuiKey_UpArrow,
15703 ImGuiKey_DownArrow};
15704 if (nav_keyboard_active)
15705 for (ImGuiKey key : nav_keyboard_keys_to_change_source)
15706 if (IsKeyDown(key))
15707 g.NavInputSource = ImGuiInputSource_Keyboard;
15708
15709 // Process navigation init request (select first/default focus)
15710 g.NavJustMovedToId = 0;
15711 g.NavJustMovedToFocusScopeId = g.NavJustMovedFromFocusScopeId = 0;
15712 if (g.NavInitResult.ID != 0)
15713 NavInitRequestApplyResult();
15714 g.NavInitRequest = false;
15715 g.NavInitRequestFromMove = false;
15716 g.NavInitResult.ID = 0;
15717
15718 // Process navigation move request
15719 if (g.NavMoveSubmitted)
15720 NavMoveRequestApplyResult();
15721 g.NavTabbingCounter = 0;
15722 g.NavMoveSubmitted = g.NavMoveScoringItems = false;
15723 if (g.NavCursorHideFrames > 0)
15724 if (--g.NavCursorHideFrames == 0)
15725 g.NavCursorVisible = true;
15726
15727 // Schedule mouse position update (will be done at the bottom of this function, after 1) processing all move
15728 // requests and 2) updating scrolling)
15729 bool set_mouse_pos = false;
15730 if (g.NavMousePosDirty && g.NavIdIsAlive)
15731 if (g.NavCursorVisible && g.NavHighlightItemUnderNav && g.NavWindow)
15732 set_mouse_pos = true;
15733 g.NavMousePosDirty = false;
15734 IM_ASSERT(g.NavLayer == ImGuiNavLayer_Main || g.NavLayer == ImGuiNavLayer_Menu);
15735
15736 // Store our return window (for returning from Menu Layer to Main Layer) and clear it as soon as we step back in our
15737 // own Layer 0
15738 if (g.NavWindow)
15739 NavSaveLastChildNavWindowIntoParent(g.NavWindow);
15740 if (g.NavWindow && g.NavWindow->NavLastChildNavWindow != NULL && g.NavLayer == ImGuiNavLayer_Main)
15741 g.NavWindow->NavLastChildNavWindow = NULL;
15742
15743 // Update CTRL+TAB and Windowing features (hold Square to move/resize/etc.)
15744 NavUpdateWindowing();
15745
15746 // Set output flags for user application
15747 io.NavActive = (nav_keyboard_active || nav_gamepad_active) && g.NavWindow &&
15748 !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs);
15749 io.NavVisible = (io.NavActive && g.NavId != 0 && g.NavCursorVisible) || (g.NavWindowingTarget != NULL);
15750
15751 // Process NavCancel input (to close a popup, get back to parent, clear focus)
15752 NavUpdateCancelRequest();
15753
15754 // Process manual activation request
15755 g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = 0;
15756 g.NavActivateFlags = ImGuiActivateFlags_None;
15757 if (g.NavId != 0 && g.NavCursorVisible && !g.NavWindowingTarget && g.NavWindow &&
15758 !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))
15759 {
15760 const bool activate_down =
15761 (nav_keyboard_active && IsKeyDown(ImGuiKey_Space, ImGuiKeyOwner_NoOwner)) ||
15762 (nav_gamepad_active && IsKeyDown(ImGuiKey_NavGamepadActivate, ImGuiKeyOwner_NoOwner));
15763 const bool activate_pressed =
15764 activate_down &&
15765 ((nav_keyboard_active && IsKeyPressed(ImGuiKey_Space, 0, ImGuiKeyOwner_NoOwner)) ||
15766 (nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadActivate, 0, ImGuiKeyOwner_NoOwner)));
15767 const bool input_down = (nav_keyboard_active && (IsKeyDown(ImGuiKey_Enter, ImGuiKeyOwner_NoOwner) ||
15768 IsKeyDown(ImGuiKey_KeypadEnter, ImGuiKeyOwner_NoOwner))) ||
15769 (nav_gamepad_active && IsKeyDown(ImGuiKey_NavGamepadInput, ImGuiKeyOwner_NoOwner));
15770 const bool input_pressed =
15771 input_down && ((nav_keyboard_active && (IsKeyPressed(ImGuiKey_Enter, 0, ImGuiKeyOwner_NoOwner) ||
15772 IsKeyPressed(ImGuiKey_KeypadEnter, 0, ImGuiKeyOwner_NoOwner))) ||
15773 (nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadInput, 0, ImGuiKeyOwner_NoOwner)));
15774 if (g.ActiveId == 0 && activate_pressed)
15775 {
15776 g.NavActivateId = g.NavId;
15777 g.NavActivateFlags = ImGuiActivateFlags_PreferTweak;
15778 }
15779 if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && input_pressed)
15780 {
15781 g.NavActivateId = g.NavId;
15782 g.NavActivateFlags = ImGuiActivateFlags_PreferInput;
15783 }
15784 if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && (activate_down || input_down))
15785 g.NavActivateDownId = g.NavId;
15786 if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && (activate_pressed || input_pressed))
15787 {
15788 g.NavActivatePressedId = g.NavId;
15789 NavHighlightActivated(g.NavId);
15790 }
15791 }
15792 if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))
15793 g.NavCursorVisible = false;
15794 else if (g.IO.ConfigNavCursorVisibleAlways && g.NavCursorHideFrames == 0)
15795 g.NavCursorVisible = true;
15796 if (g.NavActivateId != 0)
15797 IM_ASSERT(g.NavActivateDownId == g.NavActivateId);
15798
15799 // Highlight
15800 if (g.NavHighlightActivatedTimer > 0.0f)
15801 g.NavHighlightActivatedTimer = ImMax(0.0f, g.NavHighlightActivatedTimer - io.DeltaTime);
15802 if (g.NavHighlightActivatedTimer == 0.0f)
15803 g.NavHighlightActivatedId = 0;
15804
15805 // Process programmatic activation request
15806 // FIXME-NAV: Those should eventually be queued (unlike focus they don't cancel each others)
15807 if (g.NavNextActivateId != 0)
15808 {
15809 g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavNextActivateId;
15810 g.NavActivateFlags = g.NavNextActivateFlags;
15811 }
15812 g.NavNextActivateId = 0;
15813
15814 // Process move requests
15815 NavUpdateCreateMoveRequest();
15816 if (g.NavMoveDir == ImGuiDir_None)
15817 NavUpdateCreateTabbingRequest();
15818 NavUpdateAnyRequestFlag();
15819 g.NavIdIsAlive = false;
15820
15821 // Scrolling
15822 if (g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.NavWindowingTarget)
15823 {
15824 // *Fallback* manual-scroll with Nav directional keys when window has no navigable item
15825 ImGuiWindow *window = g.NavWindow;
15826 const float scroll_speed = IM_ROUND(
15827 window->FontRefSize * 100 *
15828 io.DeltaTime); // We need round the scrolling speed because sub-pixel scroll isn't reliably supported.
15829 const ImGuiDir move_dir = g.NavMoveDir;
15830 if (window->DC.NavLayersActiveMask == 0x00 && window->DC.NavWindowHasScrollY && move_dir != ImGuiDir_None)
15831 {
15832 if (move_dir == ImGuiDir_Left || move_dir == ImGuiDir_Right)
15833 SetScrollX(window,
15834 ImTrunc(window->Scroll.x + ((move_dir == ImGuiDir_Left) ? -1.0f : +1.0f) * scroll_speed));
15835 if (move_dir == ImGuiDir_Up || move_dir == ImGuiDir_Down)
15836 SetScrollY(window,
15837 ImTrunc(window->Scroll.y + ((move_dir == ImGuiDir_Up) ? -1.0f : +1.0f) * scroll_speed));
15838 }
15839
15840 // *Normal* Manual scroll with LStick
15841 // Next movement request will clamp the NavId reference rectangle to the visible area, so navigation will resume
15842 // within those bounds.
15843 if (nav_gamepad_active)
15844 {
15845 const ImVec2 scroll_dir = GetKeyMagnitude2d(ImGuiKey_GamepadLStickLeft, ImGuiKey_GamepadLStickRight,
15846 ImGuiKey_GamepadLStickUp, ImGuiKey_GamepadLStickDown);
15847 const float tweak_factor = IsKeyDown(ImGuiKey_NavGamepadTweakSlow) ? 1.0f / 10.0f
15848 : IsKeyDown(ImGuiKey_NavGamepadTweakFast) ? 10.0f
15849 : 1.0f;
15850 if (scroll_dir.x != 0.0f && window->ScrollbarX)
15851 SetScrollX(window, ImTrunc(window->Scroll.x + scroll_dir.x * scroll_speed * tweak_factor));
15852 if (scroll_dir.y != 0.0f)
15853 SetScrollY(window, ImTrunc(window->Scroll.y + scroll_dir.y * scroll_speed * tweak_factor));
15854 }
15855 }
15856
15857 // Always prioritize mouse highlight if navigation is disabled
15858 if (!nav_keyboard_active && !nav_gamepad_active)
15859 {
15860 g.NavCursorVisible = false;
15861 g.NavHighlightItemUnderNav = set_mouse_pos = false;
15862 }
15863
15864 // Update mouse position if requested
15865 // (This will take into account the possibility that a Scroll was queued in the window to offset our absolute mouse
15866 // position before scroll has been applied)
15867 if (set_mouse_pos && io.ConfigNavMoveSetMousePos && (io.BackendFlags & ImGuiBackendFlags_HasSetMousePos))
15868 TeleportMousePos(NavCalcPreferredRefPos());
15869
15870 // [DEBUG]
15871 g.NavScoringDebugCount = 0;
15872#if IMGUI_DEBUG_NAV_RECTS
15873 if (ImGuiWindow *debug_window = g.NavWindow)
15874 {
15875 ImDrawList *draw_list = GetForegroundDrawList(debug_window);
15876 int layer = g.NavLayer; /* for (int layer = 0; layer < 2; layer++)*/
15877 {
15878 ImRect r = WindowRectRelToAbs(debug_window, debug_window->NavRectRel[layer]);
15879 draw_list->AddRect(r.Min, r.Max, IM_COL32(255, 200, 0, 255));
15880 }
15881 // if (1) { ImU32 col = (!debug_window->Hidden) ? IM_COL32(255,0,255,255) : IM_COL32(255,0,0,255); ImVec2 p =
15882 // NavCalcPreferredRefPos(); char buf[32]; ImFormatString(buf, 32, "%d", g.NavLayer);
15883 // draw_list->AddCircleFilled(p, 3.0f, col); draw_list->AddText(NULL, 13.0f, p + ImVec2(8,-4), col, buf); }
15884 }
15885#endif
15886}
15887
15888void ImGui::NavInitRequestApplyResult()
15889{
15890 // In very rare cases g.NavWindow may be null (e.g. clearing focus after requesting an init request, which does
15891 // happen when releasing Alt while clicking on void)
15892 ImGuiContext &g = *GImGui;
15893 if (!g.NavWindow)
15894 return;
15895
15896 ImGuiNavItemData *result = &g.NavInitResult;
15897 if (g.NavId != result->ID)
15898 {
15899 g.NavJustMovedFromFocusScopeId = g.NavFocusScopeId;
15900 g.NavJustMovedToId = result->ID;
15901 g.NavJustMovedToFocusScopeId = result->FocusScopeId;
15902 g.NavJustMovedToKeyMods = 0;
15903 g.NavJustMovedToIsTabbing = false;
15904 g.NavJustMovedToHasSelectionData = (result->ItemFlags & ImGuiItemFlags_HasSelectionUserData) != 0;
15905 }
15906
15907 // Apply result from previous navigation init request (will typically select the first item, unless
15908 // SetItemDefaultFocus() has been called)
15909 // FIXME-NAV: On _NavFlattened windows, g.NavWindow will only be updated during subsequent frame. Not a problem
15910 // currently.
15911 IMGUI_DEBUG_LOG_NAV("[nav] NavInitRequest: ApplyResult: NavID 0x%08X in Layer %d Window \"%s\"\n", result->ID,
15912 g.NavLayer, g.NavWindow->Name);
15913 SetNavID(result->ID, g.NavLayer, result->FocusScopeId, result->RectRel);
15914 g.NavIdIsAlive = true; // Mark as alive from previous frame as we got a result
15915 if (result->SelectionUserData != ImGuiSelectionUserData_Invalid)
15916 g.NavLastValidSelectionUserData = result->SelectionUserData;
15917 if (g.NavInitRequestFromMove)
15918 SetNavCursorVisibleAfterMove();
15919}
15920
15921// Bias scoring rect ahead of scoring + update preferred pos (if missing) using source position
15922static void NavBiasScoringRect(ImRect &r, ImVec2 &preferred_pos_rel, ImGuiDir move_dir, ImGuiNavMoveFlags move_flags)
15923{
15924 // Bias initial rect
15925 ImGuiContext &g = *GImGui;
15926 const ImVec2 rel_to_abs_offset = g.NavWindow->DC.CursorStartPos;
15927
15928 // Initialize bias on departure if we don't have any. So mouse-click + arrow will record bias.
15929 // - We default to L/U bias, so moving down from a large source item into several columns will land on left-most
15930 // column.
15931 // - But each successful move sets new bias on one axis, only cleared when using mouse.
15932 if ((move_flags & ImGuiNavMoveFlags_Forwarded) == 0)
15933 {
15934 if (preferred_pos_rel.x == FLT_MAX)
15935 preferred_pos_rel.x = ImMin(r.Min.x + 1.0f, r.Max.x) - rel_to_abs_offset.x;
15936 if (preferred_pos_rel.y == FLT_MAX)
15937 preferred_pos_rel.y = r.GetCenter().y - rel_to_abs_offset.y;
15938 }
15939
15940 // Apply general bias on the other axis
15941 if ((move_dir == ImGuiDir_Up || move_dir == ImGuiDir_Down) && preferred_pos_rel.x != FLT_MAX)
15942 r.Min.x = r.Max.x = preferred_pos_rel.x + rel_to_abs_offset.x;
15943 else if ((move_dir == ImGuiDir_Left || move_dir == ImGuiDir_Right) && preferred_pos_rel.y != FLT_MAX)
15944 r.Min.y = r.Max.y = preferred_pos_rel.y + rel_to_abs_offset.y;
15945}
15946
15947void ImGui::NavUpdateCreateMoveRequest()
15948{
15949 ImGuiContext &g = *GImGui;
15950 ImGuiIO &io = g.IO;
15951 ImGuiWindow *window = g.NavWindow;
15952 const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 &&
15953 (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
15954 const bool nav_keyboard_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0;
15955
15956 if (g.NavMoveForwardToNextFrame && window != NULL)
15957 {
15958 // Forwarding previous request (which has been modified, e.g. wrap around menus rewrite the requests with a
15959 // starting rectangle at the other side of the window) (preserve most state, which were already set by the
15960 // NavMoveRequestForward() function)
15961 IM_ASSERT(g.NavMoveDir != ImGuiDir_None && g.NavMoveClipDir != ImGuiDir_None);
15962 IM_ASSERT(g.NavMoveFlags & ImGuiNavMoveFlags_Forwarded);
15963 IMGUI_DEBUG_LOG_NAV("[nav] NavMoveRequestForward %d\n", g.NavMoveDir);
15964 }
15965 else
15966 {
15967 // Initiate directional inputs request
15968 g.NavMoveDir = ImGuiDir_None;
15969 g.NavMoveFlags = ImGuiNavMoveFlags_None;
15970 g.NavMoveScrollFlags = ImGuiScrollFlags_None;
15971 if (window && !g.NavWindowingTarget && !(window->Flags & ImGuiWindowFlags_NoNavInputs))
15972 {
15973 const ImGuiInputFlags repeat_mode =
15974 ImGuiInputFlags_Repeat | (ImGuiInputFlags)ImGuiInputFlags_RepeatRateNavMove;
15975 if (!IsActiveIdUsingNavDir(ImGuiDir_Left) &&
15976 ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadLeft, repeat_mode, ImGuiKeyOwner_NoOwner)) ||
15977 (nav_keyboard_active && IsKeyPressed(ImGuiKey_LeftArrow, repeat_mode, ImGuiKeyOwner_NoOwner))))
15978 {
15979 g.NavMoveDir = ImGuiDir_Left;
15980 }
15981 if (!IsActiveIdUsingNavDir(ImGuiDir_Right) &&
15982 ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadRight, repeat_mode, ImGuiKeyOwner_NoOwner)) ||
15983 (nav_keyboard_active && IsKeyPressed(ImGuiKey_RightArrow, repeat_mode, ImGuiKeyOwner_NoOwner))))
15984 {
15985 g.NavMoveDir = ImGuiDir_Right;
15986 }
15987 if (!IsActiveIdUsingNavDir(ImGuiDir_Up) &&
15988 ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadUp, repeat_mode, ImGuiKeyOwner_NoOwner)) ||
15989 (nav_keyboard_active && IsKeyPressed(ImGuiKey_UpArrow, repeat_mode, ImGuiKeyOwner_NoOwner))))
15990 {
15991 g.NavMoveDir = ImGuiDir_Up;
15992 }
15993 if (!IsActiveIdUsingNavDir(ImGuiDir_Down) &&
15994 ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadDown, repeat_mode, ImGuiKeyOwner_NoOwner)) ||
15995 (nav_keyboard_active && IsKeyPressed(ImGuiKey_DownArrow, repeat_mode, ImGuiKeyOwner_NoOwner))))
15996 {
15997 g.NavMoveDir = ImGuiDir_Down;
15998 }
15999 }
16000 g.NavMoveClipDir = g.NavMoveDir;
16001 g.NavScoringNoClipRect = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX);
16002 }
16003
16004 // Update PageUp/PageDown/Home/End scroll
16005 // FIXME-NAV: Consider enabling those keys even without the master ImGuiConfigFlags_NavEnableKeyboard flag?
16006 float scoring_rect_offset_y = 0.0f;
16007 if (window && g.NavMoveDir == ImGuiDir_None && nav_keyboard_active)
16008 scoring_rect_offset_y = NavUpdatePageUpPageDown();
16009 if (scoring_rect_offset_y != 0.0f)
16010 {
16011 g.NavScoringNoClipRect = window->InnerRect;
16012 g.NavScoringNoClipRect.TranslateY(scoring_rect_offset_y);
16013 }
16014
16015 // [DEBUG] Always send a request when holding CTRL. Hold CTRL + Arrow change the direction.
16016#if IMGUI_DEBUG_NAV_SCORING
16017 // if (io.KeyCtrl && IsKeyPressed(ImGuiKey_C))
16018 // g.NavMoveDirForDebug = (ImGuiDir)((g.NavMoveDirForDebug + 1) & 3);
16019 if (io.KeyCtrl)
16020 {
16021 if (g.NavMoveDir == ImGuiDir_None)
16022 g.NavMoveDir = g.NavMoveDirForDebug;
16023 g.NavMoveClipDir = g.NavMoveDir;
16024 g.NavMoveFlags |= ImGuiNavMoveFlags_DebugNoResult;
16025 }
16026#endif
16027
16028 // Submit
16029 g.NavMoveForwardToNextFrame = false;
16030 if (g.NavMoveDir != ImGuiDir_None)
16031 NavMoveRequestSubmit(g.NavMoveDir, g.NavMoveClipDir, g.NavMoveFlags, g.NavMoveScrollFlags);
16032
16033 // Moving with no reference triggers an init request (will be used as a fallback if the direction fails to find a
16034 // match)
16035 if (g.NavMoveSubmitted && g.NavId == 0)
16036 {
16037 IMGUI_DEBUG_LOG_NAV("[nav] NavInitRequest: from move, window \"%s\", layer=%d\n",
16038 window ? window->Name : "<NULL>", g.NavLayer);
16039 g.NavInitRequest = g.NavInitRequestFromMove = true;
16040 g.NavInitResult.ID = 0;
16041 if (g.IO.ConfigNavCursorVisibleAuto)
16042 g.NavCursorVisible = true;
16043 }
16044
16045 // When using gamepad, we project the reference nav bounding box into window visible area.
16046 // This is to allow resuming navigation inside the visible area after doing a large amount of scrolling,
16047 // since with gamepad all movements are relative (can't focus a visible object like we can with the mouse).
16048 if (g.NavMoveSubmitted && g.NavInputSource == ImGuiInputSource_Gamepad && g.NavLayer == ImGuiNavLayer_Main &&
16049 window != NULL) // && (g.NavMoveFlags & ImGuiNavMoveFlags_Forwarded))
16050 {
16051 bool clamp_x = (g.NavMoveFlags & (ImGuiNavMoveFlags_LoopX | ImGuiNavMoveFlags_WrapX)) == 0;
16052 bool clamp_y = (g.NavMoveFlags & (ImGuiNavMoveFlags_LoopY | ImGuiNavMoveFlags_WrapY)) == 0;
16053 ImRect inner_rect_rel = WindowRectAbsToRel(
16054 window, ImRect(window->InnerRect.Min - ImVec2(1, 1), window->InnerRect.Max + ImVec2(1, 1)));
16055
16056 // Take account of changing scroll to handle triggering a new move request on a scrolling frame. (#6171)
16057 // Otherwise 'inner_rect_rel' would be off on the move result frame.
16058 inner_rect_rel.Translate(CalcNextScrollFromScrollTargetAndClamp(window) - window->Scroll);
16059
16060 if ((clamp_x || clamp_y) && !inner_rect_rel.Contains(window->NavRectRel[g.NavLayer]))
16061 {
16062 IMGUI_DEBUG_LOG_NAV("[nav] NavMoveRequest: clamp NavRectRel for gamepad move\n");
16063 float pad_x = ImMin(inner_rect_rel.GetWidth(), window->FontRefSize * 0.5f);
16064 float pad_y = ImMin(
16065 inner_rect_rel.GetHeight(),
16066 window->FontRefSize *
16067 0.5f); // Terrible approximation for the intent of starting navigation from first fully visible item
16068 inner_rect_rel.Min.x = clamp_x ? (inner_rect_rel.Min.x + pad_x) : -FLT_MAX;
16069 inner_rect_rel.Max.x = clamp_x ? (inner_rect_rel.Max.x - pad_x) : +FLT_MAX;
16070 inner_rect_rel.Min.y = clamp_y ? (inner_rect_rel.Min.y + pad_y) : -FLT_MAX;
16071 inner_rect_rel.Max.y = clamp_y ? (inner_rect_rel.Max.y - pad_y) : +FLT_MAX;
16072 window->NavRectRel[g.NavLayer].ClipWithFull(inner_rect_rel);
16073 g.NavId = 0;
16074 }
16075 }
16076
16077 // For scoring we use a single segment on the left side our current item bounding box (not touching the edge to
16078 // avoid box overlap with zero-spaced items)
16079 ImRect scoring_rect;
16080 if (window != NULL)
16081 {
16082 ImRect nav_rect_rel =
16083 !window->NavRectRel[g.NavLayer].IsInverted() ? window->NavRectRel[g.NavLayer] : ImRect(0, 0, 0, 0);
16084 scoring_rect = WindowRectRelToAbs(window, nav_rect_rel);
16085 scoring_rect.TranslateY(scoring_rect_offset_y);
16086 if (g.NavMoveSubmitted)
16087 NavBiasScoringRect(scoring_rect, window->RootWindowForNav->NavPreferredScoringPosRel[g.NavLayer],
16088 g.NavMoveDir, g.NavMoveFlags);
16089 IM_ASSERT(!scoring_rect.IsInverted()); // Ensure we have a non-inverted bounding box here will allow us to
16090 // remove extraneous ImFabs() calls in NavScoreItem().
16091 // GetForegroundDrawList()->AddRect(scoring_rect.Min, scoring_rect.Max, IM_COL32(255,200,0,255)); // [DEBUG]
16092 // if (!g.NavScoringNoClipRect.IsInverted()) { GetForegroundDrawList()->AddRect(g.NavScoringNoClipRect.Min,
16093 // g.NavScoringNoClipRect.Max, IM_COL32(255, 200, 0, 255)); } // [DEBUG]
16094 }
16095 g.NavScoringRect = scoring_rect;
16096 g.NavScoringNoClipRect.Add(scoring_rect);
16097}
16098
16099void ImGui::NavUpdateCreateTabbingRequest()
16100{
16101 ImGuiContext &g = *GImGui;
16102 ImGuiWindow *window = g.NavWindow;
16103 IM_ASSERT(g.NavMoveDir == ImGuiDir_None);
16104 if (window == NULL || g.NavWindowingTarget != NULL || (window->Flags & ImGuiWindowFlags_NoNavInputs))
16105 return;
16106
16107 const bool tab_pressed =
16108 IsKeyPressed(ImGuiKey_Tab, ImGuiInputFlags_Repeat, ImGuiKeyOwner_NoOwner) && !g.IO.KeyCtrl && !g.IO.KeyAlt;
16109 if (!tab_pressed)
16110 return;
16111
16112 // Initiate tabbing request
16113 // (this is ALWAYS ENABLED, regardless of ImGuiConfigFlags_NavEnableKeyboard flag!)
16114 // See NavProcessItemForTabbingRequest() for a description of the various forward/backward tabbing cases with and
16115 // without wrapping.
16116 const bool nav_keyboard_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0;
16117 if (nav_keyboard_active)
16118 g.NavTabbingDir = g.IO.KeyShift ? -1 : (g.NavCursorVisible == false && g.ActiveId == 0) ? 0 : +1;
16119 else
16120 g.NavTabbingDir = g.IO.KeyShift ? -1 : (g.ActiveId == 0) ? 0 : +1;
16121 ImGuiNavMoveFlags move_flags = ImGuiNavMoveFlags_IsTabbing | ImGuiNavMoveFlags_Activate;
16122 ImGuiScrollFlags scroll_flags = window->Appearing
16123 ? ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_AlwaysCenterY
16124 : ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_KeepVisibleEdgeY;
16125 ImGuiDir clip_dir = (g.NavTabbingDir < 0) ? ImGuiDir_Up : ImGuiDir_Down;
16126 NavMoveRequestSubmit(
16127 ImGuiDir_None, clip_dir, move_flags,
16128 scroll_flags); // FIXME-NAV: Once we refactor tabbing, add LegacyApi flag to not activate non-inputable.
16129 g.NavTabbingCounter = -1;
16130}
16131
16132// Apply result from previous frame navigation directional move request. Always called from NavUpdate()
16133void ImGui::NavMoveRequestApplyResult()
16134{
16135 ImGuiContext &g = *GImGui;
16136#if IMGUI_DEBUG_NAV_SCORING
16137 if (g.NavMoveFlags & ImGuiNavMoveFlags_DebugNoResult) // [DEBUG] Scoring all items in NavWindow at all times
16138 return;
16139#endif
16140
16141 // Select which result to use
16142 ImGuiNavItemData *result = (g.NavMoveResultLocal.ID != 0) ? &g.NavMoveResultLocal
16143 : (g.NavMoveResultOther.ID != 0) ? &g.NavMoveResultOther
16144 : NULL;
16145
16146 // Tabbing forward wrap
16147 if ((g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) && result == NULL)
16148 if ((g.NavTabbingCounter == 1 || g.NavTabbingDir == 0) && g.NavTabbingResultFirst.ID)
16149 result = &g.NavTabbingResultFirst;
16150
16151 // In a situation when there are no results but NavId != 0, re-enable the Navigation highlight (because g.NavId is
16152 // not considered as a possible result)
16153 const ImGuiAxis axis = (g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) ? ImGuiAxis_Y : ImGuiAxis_X;
16154 if (result == NULL)
16155 {
16156 if (g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing)
16157 g.NavMoveFlags |= ImGuiNavMoveFlags_NoSetNavCursorVisible;
16158 if (g.NavId != 0 && (g.NavMoveFlags & ImGuiNavMoveFlags_NoSetNavCursorVisible) == 0)
16159 SetNavCursorVisibleAfterMove();
16160 NavClearPreferredPosForAxis(axis); // On a failed move, clear preferred pos for this axis.
16161 IMGUI_DEBUG_LOG_NAV("[nav] NavMoveSubmitted but not led to a result!\n");
16162 return;
16163 }
16164
16165 // PageUp/PageDown behavior first jumps to the bottom/top mostly visible item, _otherwise_ use the result from the
16166 // previous/next page.
16167 if (g.NavMoveFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet)
16168 if (g.NavMoveResultLocalVisible.ID != 0 && g.NavMoveResultLocalVisible.ID != g.NavId)
16169 result = &g.NavMoveResultLocalVisible;
16170
16171 // Maybe entering a flattened child from the outside? In this case solve the tie using the regular scoring rules.
16172 if (result != &g.NavMoveResultOther && g.NavMoveResultOther.ID != 0 &&
16173 g.NavMoveResultOther.Window->ParentWindow == g.NavWindow)
16174 if ((g.NavMoveResultOther.DistBox < result->DistBox) ||
16175 (g.NavMoveResultOther.DistBox == result->DistBox && g.NavMoveResultOther.DistCenter < result->DistCenter))
16176 result = &g.NavMoveResultOther;
16177 IM_ASSERT(g.NavWindow && result->Window);
16178
16179 // Scroll to keep newly navigated item fully into view.
16180 if (g.NavLayer == ImGuiNavLayer_Main)
16181 {
16182 ImRect rect_abs = WindowRectRelToAbs(result->Window, result->RectRel);
16183 ScrollToRectEx(result->Window, rect_abs, g.NavMoveScrollFlags);
16184
16185 if (g.NavMoveFlags & ImGuiNavMoveFlags_ScrollToEdgeY)
16186 {
16187 // FIXME: Should remove this? Or make more precise: use ScrollToRectEx() with edge?
16188 float scroll_target = (g.NavMoveDir == ImGuiDir_Up) ? result->Window->ScrollMax.y : 0.0f;
16189 SetScrollY(result->Window, scroll_target);
16190 }
16191 }
16192
16193 if (g.NavWindow != result->Window)
16194 {
16195 IMGUI_DEBUG_LOG_FOCUS("[focus] NavMoveRequest: SetNavWindow(\"%s\")\n", result->Window->Name);
16196 g.NavWindow = result->Window;
16197 g.NavLastValidSelectionUserData = ImGuiSelectionUserData_Invalid;
16198 }
16199
16200 // Clear active id unless requested not to
16201 // FIXME: ImGuiNavMoveFlags_NoClearActiveId is currently unused as we don't have a clear strategy to preserve active
16202 // id after interaction, so this is mostly provided as a gateway for further experiments (see #1418, #2890)
16203 if (g.ActiveId != result->ID && (g.NavMoveFlags & ImGuiNavMoveFlags_NoClearActiveId) == 0)
16204 ClearActiveID();
16205
16206 // Don't set NavJustMovedToId if just landed on the same spot (which may happen with
16207 // ImGuiNavMoveFlags_AllowCurrentNavId) PageUp/PageDown however sets always set NavJustMovedTo (vs Home/End which
16208 // doesn't) mimicking Windows behavior.
16209 if ((g.NavId != result->ID || (g.NavMoveFlags & ImGuiNavMoveFlags_IsPageMove)) &&
16210 (g.NavMoveFlags & ImGuiNavMoveFlags_NoSelect) == 0)
16211 {
16212 g.NavJustMovedFromFocusScopeId = g.NavFocusScopeId;
16213 g.NavJustMovedToId = result->ID;
16214 g.NavJustMovedToFocusScopeId = result->FocusScopeId;
16215 g.NavJustMovedToKeyMods = g.NavMoveKeyMods;
16216 g.NavJustMovedToIsTabbing = (g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) != 0;
16217 g.NavJustMovedToHasSelectionData = (result->ItemFlags & ImGuiItemFlags_HasSelectionUserData) != 0;
16218 // IMGUI_DEBUG_LOG_NAV("[nav] NavJustMovedFromFocusScopeId = 0x%08X, NavJustMovedToFocusScopeId = 0x%08X\n",
16219 // g.NavJustMovedFromFocusScopeId, g.NavJustMovedToFocusScopeId);
16220 }
16221
16222 // Apply new NavID/Focus
16223 IMGUI_DEBUG_LOG_NAV("[nav] NavMoveRequest: result NavID 0x%08X in Layer %d Window \"%s\"\n", result->ID, g.NavLayer,
16224 g.NavWindow->Name);
16225 ImVec2 preferred_scoring_pos_rel = g.NavWindow->RootWindowForNav->NavPreferredScoringPosRel[g.NavLayer];
16226 SetNavID(result->ID, g.NavLayer, result->FocusScopeId, result->RectRel);
16227 if (result->SelectionUserData != ImGuiSelectionUserData_Invalid)
16228 g.NavLastValidSelectionUserData = result->SelectionUserData;
16229
16230 // Restore last preferred position for current axis
16231 // (storing in RootWindowForNav-> as the info is desirable at the beginning of a Move Request. In theory all storage
16232 // should use RootWindowForNav..)
16233 if ((g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) == 0)
16234 {
16235 preferred_scoring_pos_rel[axis] = result->RectRel.GetCenter()[axis];
16236 g.NavWindow->RootWindowForNav->NavPreferredScoringPosRel[g.NavLayer] = preferred_scoring_pos_rel;
16237 }
16238
16239 // Tabbing: Activates Inputable, otherwise only Focus
16240 if ((g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) && (result->ItemFlags & ImGuiItemFlags_Inputable) == 0)
16241 g.NavMoveFlags &= ~ImGuiNavMoveFlags_Activate;
16242
16243 // Activate
16244 if (g.NavMoveFlags & ImGuiNavMoveFlags_Activate)
16245 {
16246 g.NavNextActivateId = result->ID;
16247 g.NavNextActivateFlags = ImGuiActivateFlags_None;
16248 if (g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing)
16249 g.NavNextActivateFlags |=
16250 ImGuiActivateFlags_PreferInput | ImGuiActivateFlags_TryToPreserveState | ImGuiActivateFlags_FromTabbing;
16251 }
16252
16253 // Make nav cursor visible
16254 if ((g.NavMoveFlags & ImGuiNavMoveFlags_NoSetNavCursorVisible) == 0)
16255 SetNavCursorVisibleAfterMove();
16256}
16257
16258// Process Escape/NavCancel input (to close a popup, get back to parent, clear focus)
16259// FIXME: In order to support e.g. Escape to clear a selection we'll need:
16260// - either to store the equivalent of ActiveIdUsingKeyInputMask for a FocusScope and test for it.
16261// - either to move most/all of those tests to the epilogue/end functions of the scope they are dealing with (e.g. exit
16262// child window in EndChild()) or in EndFrame(), to allow an earlier intercept
16263static void ImGui::NavUpdateCancelRequest()
16264{
16265 ImGuiContext &g = *GImGui;
16266 const bool nav_gamepad_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 &&
16267 (g.IO.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
16268 const bool nav_keyboard_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0;
16269 if (!(nav_keyboard_active && IsKeyPressed(ImGuiKey_Escape, 0, ImGuiKeyOwner_NoOwner)) &&
16270 !(nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadCancel, 0, ImGuiKeyOwner_NoOwner)))
16271 return;
16272
16273 IMGUI_DEBUG_LOG_NAV("[nav] NavUpdateCancelRequest()\n");
16274 if (g.ActiveId != 0)
16275 {
16276 ClearActiveID();
16277 }
16278 else if (g.NavLayer != ImGuiNavLayer_Main)
16279 {
16280 // Leave the "menu" layer
16281 NavRestoreLayer(ImGuiNavLayer_Main);
16282 SetNavCursorVisibleAfterMove();
16283 }
16284 else if (g.NavWindow && g.NavWindow != g.NavWindow->RootWindow &&
16285 !(g.NavWindow->RootWindowForNav->Flags & ImGuiWindowFlags_Popup) &&
16286 g.NavWindow->RootWindowForNav->ParentWindow)
16287 {
16288 // Exit child window
16289 ImGuiWindow *child_window = g.NavWindow->RootWindowForNav;
16290 ImGuiWindow *parent_window = child_window->ParentWindow;
16291 IM_ASSERT(child_window->ChildId != 0);
16292 FocusWindow(parent_window);
16293 SetNavID(child_window->ChildId, ImGuiNavLayer_Main, 0, WindowRectAbsToRel(parent_window, child_window->Rect()));
16294 SetNavCursorVisibleAfterMove();
16295 }
16296 else if (g.OpenPopupStack.Size > 0 && g.OpenPopupStack.back().Window != NULL &&
16297 !(g.OpenPopupStack.back().Window->Flags & ImGuiWindowFlags_Modal))
16298 {
16299 // Close open popup/menu
16300 ClosePopupToLevel(g.OpenPopupStack.Size - 1, true);
16301 }
16302 else
16303 {
16304 // Clear NavLastId for popups but keep it for regular child window so we can leave one and come back where we
16305 // were
16306 // FIXME-NAV: This should happen on window appearing.
16307 if (g.IO.ConfigNavEscapeClearFocusItem || g.IO.ConfigNavEscapeClearFocusWindow)
16308 if (g.NavWindow && ((g.NavWindow->Flags &
16309 ImGuiWindowFlags_Popup))) // || !(g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow)))
16310 g.NavWindow->NavLastIds[0] = 0;
16311
16312 // Clear nav focus
16313 if (g.IO.ConfigNavEscapeClearFocusItem || g.IO.ConfigNavEscapeClearFocusWindow)
16314 g.NavId = 0;
16315 if (g.IO.ConfigNavEscapeClearFocusWindow)
16316 FocusWindow(NULL);
16317 }
16318}
16319
16320// Handle PageUp/PageDown/Home/End keys
16321// Called from NavUpdateCreateMoveRequest() which will use our output to create a move request
16322// FIXME-NAV: This doesn't work properly with NavFlattened siblings as we use NavWindow rectangle for reference
16323// FIXME-NAV: how to get Home/End to aim at the beginning/end of a 2D grid?
16324static float ImGui::NavUpdatePageUpPageDown()
16325{
16326 ImGuiContext &g = *GImGui;
16327 ImGuiWindow *window = g.NavWindow;
16328 if ((window->Flags & ImGuiWindowFlags_NoNavInputs) || g.NavWindowingTarget != NULL)
16329 return 0.0f;
16330
16331 const bool page_up_held = IsKeyDown(ImGuiKey_PageUp, ImGuiKeyOwner_NoOwner);
16332 const bool page_down_held = IsKeyDown(ImGuiKey_PageDown, ImGuiKeyOwner_NoOwner);
16333 const bool home_pressed = IsKeyPressed(ImGuiKey_Home, ImGuiInputFlags_Repeat, ImGuiKeyOwner_NoOwner);
16334 const bool end_pressed = IsKeyPressed(ImGuiKey_End, ImGuiInputFlags_Repeat, ImGuiKeyOwner_NoOwner);
16335 if (page_up_held == page_down_held &&
16336 home_pressed == end_pressed) // Proceed if either (not both) are pressed, otherwise early out
16337 return 0.0f;
16338
16339 if (g.NavLayer != ImGuiNavLayer_Main)
16340 NavRestoreLayer(ImGuiNavLayer_Main);
16341
16342 if (window->DC.NavLayersActiveMask == 0x00 && window->DC.NavWindowHasScrollY)
16343 {
16344 // Fallback manual-scroll when window has no navigable item
16345 if (IsKeyPressed(ImGuiKey_PageUp, ImGuiInputFlags_Repeat, ImGuiKeyOwner_NoOwner))
16346 SetScrollY(window, window->Scroll.y - window->InnerRect.GetHeight());
16347 else if (IsKeyPressed(ImGuiKey_PageDown, ImGuiInputFlags_Repeat, ImGuiKeyOwner_NoOwner))
16348 SetScrollY(window, window->Scroll.y + window->InnerRect.GetHeight());
16349 else if (home_pressed)
16350 SetScrollY(window, 0.0f);
16351 else if (end_pressed)
16352 SetScrollY(window, window->ScrollMax.y);
16353 }
16354 else
16355 {
16356 ImRect &nav_rect_rel = window->NavRectRel[g.NavLayer];
16357 const float page_offset_y =
16358 ImMax(0.0f, window->InnerRect.GetHeight() - window->FontRefSize * 1.0f + nav_rect_rel.GetHeight());
16359 float nav_scoring_rect_offset_y = 0.0f;
16360 if (IsKeyPressed(ImGuiKey_PageUp, true))
16361 {
16362 nav_scoring_rect_offset_y = -page_offset_y;
16363 g.NavMoveDir = ImGuiDir_Down; // Because our scoring rect is offset up, we request the down direction (so we
16364 // can always land on the last item)
16365 g.NavMoveClipDir = ImGuiDir_Up;
16366 g.NavMoveFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet |
16367 ImGuiNavMoveFlags_IsPageMove;
16368 }
16369 else if (IsKeyPressed(ImGuiKey_PageDown, true))
16370 {
16371 nav_scoring_rect_offset_y = +page_offset_y;
16372 g.NavMoveDir = ImGuiDir_Up; // Because our scoring rect is offset down, we request the up direction (so we
16373 // can always land on the last item)
16374 g.NavMoveClipDir = ImGuiDir_Down;
16375 g.NavMoveFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet |
16376 ImGuiNavMoveFlags_IsPageMove;
16377 }
16378 else if (home_pressed)
16379 {
16380 // FIXME-NAV: handling of Home/End is assuming that the top/bottom most item will be visible with Scroll.y
16381 // == 0/ScrollMax.y Scrolling will be handled via the ImGuiNavMoveFlags_ScrollToEdgeY flag, we don't scroll
16382 // immediately to avoid scrolling happening before nav result. Preserve current horizontal position if we
16383 // have any.
16384 nav_rect_rel.Min.y = nav_rect_rel.Max.y = 0.0f;
16385 if (nav_rect_rel.IsInverted())
16386 nav_rect_rel.Min.x = nav_rect_rel.Max.x = 0.0f;
16387 g.NavMoveDir = ImGuiDir_Down;
16388 g.NavMoveFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_ScrollToEdgeY;
16389 // FIXME-NAV: MoveClipDir left to _None, intentional?
16390 }
16391 else if (end_pressed)
16392 {
16393 nav_rect_rel.Min.y = nav_rect_rel.Max.y = window->ContentSize.y;
16394 if (nav_rect_rel.IsInverted())
16395 nav_rect_rel.Min.x = nav_rect_rel.Max.x = 0.0f;
16396 g.NavMoveDir = ImGuiDir_Up;
16397 g.NavMoveFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_ScrollToEdgeY;
16398 // FIXME-NAV: MoveClipDir left to _None, intentional?
16399 }
16400 return nav_scoring_rect_offset_y;
16401 }
16402 return 0.0f;
16403}
16404
16405static void ImGui::NavEndFrame()
16406{
16407 ImGuiContext &g = *GImGui;
16408
16409 // Show CTRL+TAB list window
16410 if (g.NavWindowingTarget != NULL)
16411 NavUpdateWindowingOverlay();
16412
16413 // Perform wrap-around in menus
16414 // FIXME-NAV: Wrap may need to apply a weight bias on the other axis. e.g. 4x4 grid with 2 last items missing on
16415 // last item won't handle LoopY/WrapY correctly.
16416 // FIXME-NAV: Wrap (not Loop) support could be handled by the scoring function and then WrapX would function without
16417 // an extra frame.
16418 if (g.NavWindow && NavMoveRequestButNoResultYet() && (g.NavMoveFlags & ImGuiNavMoveFlags_WrapMask_) &&
16419 (g.NavMoveFlags & ImGuiNavMoveFlags_Forwarded) == 0)
16420 NavUpdateCreateWrappingRequest();
16421}
16422
16423static void ImGui::NavUpdateCreateWrappingRequest()
16424{
16425 ImGuiContext &g = *GImGui;
16426 ImGuiWindow *window = g.NavWindow;
16427
16428 bool do_forward = false;
16429 ImRect bb_rel = window->NavRectRel[g.NavLayer];
16430 ImGuiDir clip_dir = g.NavMoveDir;
16431
16432 const ImGuiNavMoveFlags move_flags = g.NavMoveFlags;
16433 // const ImGuiAxis move_axis = (g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) ? ImGuiAxis_Y :
16434 // ImGuiAxis_X;
16435 if (g.NavMoveDir == ImGuiDir_Left && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX)))
16436 {
16437 bb_rel.Min.x = bb_rel.Max.x = window->ContentSize.x + window->WindowPadding.x;
16438 if (move_flags & ImGuiNavMoveFlags_WrapX)
16439 {
16440 bb_rel.TranslateY(-bb_rel.GetHeight()); // Previous row
16441 clip_dir = ImGuiDir_Up;
16442 }
16443 do_forward = true;
16444 }
16445 if (g.NavMoveDir == ImGuiDir_Right && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX)))
16446 {
16447 bb_rel.Min.x = bb_rel.Max.x = -window->WindowPadding.x;
16448 if (move_flags & ImGuiNavMoveFlags_WrapX)
16449 {
16450 bb_rel.TranslateY(+bb_rel.GetHeight()); // Next row
16451 clip_dir = ImGuiDir_Down;
16452 }
16453 do_forward = true;
16454 }
16455 if (g.NavMoveDir == ImGuiDir_Up && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY)))
16456 {
16457 bb_rel.Min.y = bb_rel.Max.y = window->ContentSize.y + window->WindowPadding.y;
16458 if (move_flags & ImGuiNavMoveFlags_WrapY)
16459 {
16460 bb_rel.TranslateX(-bb_rel.GetWidth()); // Previous column
16461 clip_dir = ImGuiDir_Left;
16462 }
16463 do_forward = true;
16464 }
16465 if (g.NavMoveDir == ImGuiDir_Down && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY)))
16466 {
16467 bb_rel.Min.y = bb_rel.Max.y = -window->WindowPadding.y;
16468 if (move_flags & ImGuiNavMoveFlags_WrapY)
16469 {
16470 bb_rel.TranslateX(+bb_rel.GetWidth()); // Next column
16471 clip_dir = ImGuiDir_Right;
16472 }
16473 do_forward = true;
16474 }
16475 if (!do_forward)
16476 return;
16477 window->NavRectRel[g.NavLayer] = bb_rel;
16478 NavClearPreferredPosForAxis(ImGuiAxis_X);
16479 NavClearPreferredPosForAxis(ImGuiAxis_Y);
16480 NavMoveRequestForward(g.NavMoveDir, clip_dir, move_flags, g.NavMoveScrollFlags);
16481}
16482
16483// Can we focus this window with CTRL+TAB (or PadMenu + PadFocusPrev/PadFocusNext)
16484// Note that NoNavFocus makes the window not reachable with CTRL+TAB but it can still be focused with mouse or
16485// programmatically. If you want a window to never be focused, you may use the e.g. NoInputs flag.
16486bool ImGui::IsWindowNavFocusable(ImGuiWindow *window)
16487{
16488 return window->WasActive && window == window->RootWindow && !(window->Flags & ImGuiWindowFlags_NoNavFocus);
16489}
16490
16491static ImGuiWindow *FindWindowNavFocusable(int i_start, int i_stop, int dir) // FIXME-OPT O(N)
16492{
16493 ImGuiContext &g = *GImGui;
16494 for (int i = i_start; i >= 0 && i < g.WindowsFocusOrder.Size && i != i_stop; i += dir)
16495 if (ImGui::IsWindowNavFocusable(g.WindowsFocusOrder[i]))
16496 return g.WindowsFocusOrder[i];
16497 return NULL;
16498}
16499
16500static void NavUpdateWindowingTarget(int focus_change_dir)
16501{
16502 ImGuiContext &g = *GImGui;
16503 IM_ASSERT(g.NavWindowingTarget);
16504 if (g.NavWindowingTarget->Flags & ImGuiWindowFlags_Modal)
16505 return;
16506
16507 const int i_current = ImGui::FindWindowFocusIndex(g.NavWindowingTarget);
16508 ImGuiWindow *window_target = FindWindowNavFocusable(i_current + focus_change_dir, -INT_MAX, focus_change_dir);
16509 if (!window_target)
16510 window_target = FindWindowNavFocusable((focus_change_dir < 0) ? (g.WindowsFocusOrder.Size - 1) : 0, i_current,
16511 focus_change_dir);
16512 if (window_target) // Don't reset windowing target if there's a single window in the list
16513 {
16514 g.NavWindowingTarget = g.NavWindowingTargetAnim = window_target;
16515 g.NavWindowingAccumDeltaPos = g.NavWindowingAccumDeltaSize = ImVec2(0.0f, 0.0f);
16516 }
16517 g.NavWindowingToggleLayer = false;
16518}
16519
16520// Apply focus and close overlay
16521static void ImGui::NavUpdateWindowingApplyFocus(ImGuiWindow *apply_focus_window)
16522{
16523 // FIXME: Many actions here could be part of a higher-level/reused function. Why aren't they in FocusWindow() ?
16524 // Investigate for each of them: ClearActiveID(), NavRestoreHighlightAfterMove(), NavRestoreLastChildNavWindow(),
16525 // ClosePopupsOverWindow(), NavInitWindow()
16526 ImGuiContext &g = *GImGui;
16527 if (g.NavWindow == NULL || apply_focus_window != g.NavWindow->RootWindow)
16528 {
16529 ImGuiViewport *previous_viewport = g.NavWindow ? g.NavWindow->Viewport : NULL;
16530 ClearActiveID();
16531 SetNavCursorVisibleAfterMove();
16532 ClosePopupsOverWindow(apply_focus_window, false);
16533 FocusWindow(apply_focus_window, ImGuiFocusRequestFlags_RestoreFocusedChild);
16534 apply_focus_window = g.NavWindow;
16535 if (apply_focus_window->NavLastIds[0] == 0)
16536 NavInitWindow(apply_focus_window, false);
16537
16538 // If the window has ONLY a menu layer (no main layer), select it directly
16539 // Use NavLayersActiveMaskNext since windows didn't have a chance to be Begin()-ed on this frame,
16540 // so CTRL+Tab where the keys are only held for 1 frame will be able to use correct layers mask since
16541 // the target window as already been previewed once.
16542 // FIXME-NAV: This should be done in NavInit.. or in FocusWindow... However in both of those cases,
16543 // we won't have a guarantee that windows has been visible before and therefore NavLayersActiveMask*
16544 // won't be valid.
16545 if (apply_focus_window->DC.NavLayersActiveMaskNext == (1 << ImGuiNavLayer_Menu))
16546 g.NavLayer = ImGuiNavLayer_Menu;
16547
16548 // Request OS level focus
16549 if (apply_focus_window->Viewport != previous_viewport && g.PlatformIO.Platform_SetWindowFocus)
16550 g.PlatformIO.Platform_SetWindowFocus(apply_focus_window->Viewport);
16551 }
16552 g.NavWindowingTarget = NULL;
16553}
16554
16555// Windowing management mode
16556// Keyboard: CTRL+Tab (change focus/move/resize), Alt (toggle menu layer)
16557// Gamepad: Hold Menu/Square (change focus/move/resize), Tap Menu/Square (toggle menu layer)
16558static void ImGui::NavUpdateWindowing()
16559{
16560 ImGuiContext &g = *GImGui;
16561 ImGuiIO &io = g.IO;
16562
16563 ImGuiWindow *apply_focus_window = NULL;
16564 bool apply_toggle_layer = false;
16565
16566 ImGuiWindow *modal_window = GetTopMostPopupModal();
16567 bool allow_windowing = (modal_window == NULL); // FIXME: This prevent CTRL+TAB from being usable with windows that
16568 // are inside the Begin-stack of that modal.
16569 if (!allow_windowing)
16570 g.NavWindowingTarget = NULL;
16571
16572 // Fade out
16573 if (g.NavWindowingTargetAnim && g.NavWindowingTarget == NULL)
16574 {
16575 g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha - io.DeltaTime * 10.0f, 0.0f);
16576 if (g.DimBgRatio <= 0.0f && g.NavWindowingHighlightAlpha <= 0.0f)
16577 g.NavWindowingTargetAnim = NULL;
16578 }
16579
16580 // Start CTRL+Tab or Square+L/R window selection
16581 // (g.ConfigNavWindowingKeyNext/g.ConfigNavWindowingKeyPrev defaults are ImGuiMod_Ctrl|ImGuiKey_Tab and
16582 // ImGuiMod_Ctrl|ImGuiMod_Shift|ImGuiKey_Tab)
16583 const ImGuiID owner_id = ImHashStr("##NavUpdateWindowing");
16584 const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 &&
16585 (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
16586 const bool nav_keyboard_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0;
16587 const bool keyboard_next_window =
16588 allow_windowing && g.ConfigNavWindowingKeyNext &&
16589 Shortcut(g.ConfigNavWindowingKeyNext, ImGuiInputFlags_Repeat | ImGuiInputFlags_RouteAlways, owner_id);
16590 const bool keyboard_prev_window =
16591 allow_windowing && g.ConfigNavWindowingKeyPrev &&
16592 Shortcut(g.ConfigNavWindowingKeyPrev, ImGuiInputFlags_Repeat | ImGuiInputFlags_RouteAlways, owner_id);
16593 const bool start_windowing_with_gamepad = allow_windowing && nav_gamepad_active && !g.NavWindowingTarget &&
16594 Shortcut(ImGuiKey_NavGamepadMenu, ImGuiInputFlags_RouteAlways, owner_id);
16595 const bool start_windowing_with_keyboard =
16596 allow_windowing && !g.NavWindowingTarget &&
16597 (keyboard_next_window || keyboard_prev_window); // Note: enabled even without NavEnableKeyboard!
16598 bool just_started_windowing_from_null_focus = false;
16599 if (start_windowing_with_gamepad || start_windowing_with_keyboard)
16600 if (ImGuiWindow *window =
16601 g.NavWindow ? g.NavWindow : FindWindowNavFocusable(g.WindowsFocusOrder.Size - 1, -INT_MAX, -1))
16602 {
16603 if (start_windowing_with_keyboard || g.ConfigNavWindowingWithGamepad)
16604 g.NavWindowingTarget = g.NavWindowingTargetAnim = window->RootWindow; // Current location
16605 g.NavWindowingTimer = g.NavWindowingHighlightAlpha = 0.0f;
16606 g.NavWindowingAccumDeltaPos = g.NavWindowingAccumDeltaSize = ImVec2(0.0f, 0.0f);
16607 g.NavWindowingToggleLayer = start_windowing_with_gamepad ? true : false; // Gamepad starts toggling layer
16608 g.NavWindowingInputSource = g.NavInputSource =
16609 start_windowing_with_keyboard ? ImGuiInputSource_Keyboard : ImGuiInputSource_Gamepad;
16610 if (g.NavWindow == NULL)
16611 just_started_windowing_from_null_focus = true;
16612
16613 // Manually register ownership of our mods. Using a global route in the Shortcut() calls instead would
16614 // probably be correct but may have more side-effects.
16615 if (keyboard_next_window || keyboard_prev_window)
16616 SetKeyOwnersForKeyChord((g.ConfigNavWindowingKeyNext | g.ConfigNavWindowingKeyPrev) & ImGuiMod_Mask_,
16617 owner_id);
16618 }
16619
16620 // Gamepad update
16621 if ((g.NavWindowingTarget || g.NavWindowingToggleLayer) && g.NavWindowingInputSource == ImGuiInputSource_Gamepad)
16622 {
16623 if (g.NavWindowingTarget != NULL)
16624 {
16625 // Highlight only appears after a brief time holding the button, so that a fast tap on
16626 // ImGuiKey_NavGamepadMenu (to toggle NavLayer) doesn't add visual noise However inputs are accepted
16627 // immediately, so you press ImGuiKey_NavGamepadMenu + L1/R1 fast.
16628 g.NavWindowingTimer += io.DeltaTime;
16629 g.NavWindowingHighlightAlpha =
16630 ImMax(g.NavWindowingHighlightAlpha,
16631 ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f));
16632
16633 // Select window to focus
16634 const int focus_change_dir = (int)IsKeyPressed(ImGuiKey_GamepadL1) - (int)IsKeyPressed(ImGuiKey_GamepadR1);
16635 if (focus_change_dir != 0 && !just_started_windowing_from_null_focus)
16636 {
16637 NavUpdateWindowingTarget(focus_change_dir);
16638 g.NavWindowingHighlightAlpha = 1.0f;
16639 }
16640 }
16641
16642 // Single press toggles NavLayer, long press with L/R apply actual focus on release (until then the window was
16643 // merely rendered top-most)
16644 if (!IsKeyDown(ImGuiKey_NavGamepadMenu))
16645 {
16646 g.NavWindowingToggleLayer &=
16647 (g.NavWindowingHighlightAlpha <
16648 1.0f); // Once button was held long enough we don't consider it a tap-to-toggle-layer press anymore.
16649 if (g.NavWindowingToggleLayer && g.NavWindow)
16650 apply_toggle_layer = true;
16651 else if (!g.NavWindowingToggleLayer)
16652 apply_focus_window = g.NavWindowingTarget;
16653 g.NavWindowingTarget = NULL;
16654 g.NavWindowingToggleLayer = false;
16655 }
16656 }
16657
16658 // Keyboard: Focus
16659 if (g.NavWindowingTarget && g.NavWindowingInputSource == ImGuiInputSource_Keyboard)
16660 {
16661 // Visuals only appears after a brief time after pressing TAB the first time, so that a fast CTRL+TAB doesn't
16662 // add visual noise
16663 ImGuiKeyChord shared_mods = ((g.ConfigNavWindowingKeyNext ? g.ConfigNavWindowingKeyNext : ImGuiMod_Mask_) &
16664 (g.ConfigNavWindowingKeyPrev ? g.ConfigNavWindowingKeyPrev : ImGuiMod_Mask_)) &
16665 ImGuiMod_Mask_;
16666 IM_ASSERT(shared_mods != 0); // Next/Prev shortcut currently needs a shared modifier to "hold", otherwise Prev
16667 // actions would keep cycling between two windows.
16668 g.NavWindowingTimer += io.DeltaTime;
16669 g.NavWindowingHighlightAlpha =
16670 ImMax(g.NavWindowingHighlightAlpha,
16671 ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f)); // 1.0f
16672 if ((keyboard_next_window || keyboard_prev_window) && !just_started_windowing_from_null_focus)
16673 NavUpdateWindowingTarget(keyboard_next_window ? -1 : +1);
16674 else if ((io.KeyMods & shared_mods) != shared_mods)
16675 apply_focus_window = g.NavWindowingTarget;
16676 }
16677
16678 // Keyboard: Press and Release ALT to toggle menu layer
16679 const ImGuiKey windowing_toggle_keys[] = {ImGuiKey_LeftAlt, ImGuiKey_RightAlt};
16680 bool windowing_toggle_layer_start = false;
16681 if (g.NavWindow != NULL && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))
16682 for (ImGuiKey windowing_toggle_key : windowing_toggle_keys)
16683 if (nav_keyboard_active && IsKeyPressed(windowing_toggle_key, 0, ImGuiKeyOwner_NoOwner))
16684 {
16685 windowing_toggle_layer_start = true;
16686 g.NavWindowingToggleLayer = true;
16687 g.NavWindowingToggleKey = windowing_toggle_key;
16688 g.NavWindowingInputSource = g.NavInputSource = ImGuiInputSource_Keyboard;
16689 break;
16690 }
16691 if (g.NavWindowingToggleLayer && g.NavWindowingInputSource == ImGuiInputSource_Keyboard)
16692 {
16693 // We cancel toggling nav layer when any text has been typed (generally while holding Alt). (See #370)
16694 // We cancel toggling nav layer when other modifiers are pressed. (See #4439)
16695 // - AltGR is Alt+Ctrl on some layout but we can't reliably detect it (not all backends/systems/layout emit it
16696 // as Alt+Ctrl). We cancel toggling nav layer if an owner has claimed the key.
16697 if (io.InputQueueCharacters.Size > 0 || io.KeyCtrl || io.KeyShift || io.KeySuper)
16698 g.NavWindowingToggleLayer = false;
16699 else if (windowing_toggle_layer_start == false && g.LastKeyboardKeyPressTime == g.Time)
16700 g.NavWindowingToggleLayer = false;
16701 else if (TestKeyOwner(g.NavWindowingToggleKey, ImGuiKeyOwner_NoOwner) == false ||
16702 TestKeyOwner(ImGuiMod_Alt, ImGuiKeyOwner_NoOwner) == false)
16703 g.NavWindowingToggleLayer = false;
16704
16705 // Apply layer toggle on Alt release
16706 // Important: as before version <18314 we lacked an explicit IO event for focus gain/loss, we also compare mouse
16707 // validity to detect old backends clearing mouse pos on focus loss.
16708 if (IsKeyReleased(g.NavWindowingToggleKey) && g.NavWindowingToggleLayer)
16709 if (g.ActiveId == 0 || g.ActiveIdAllowOverlap)
16710 if (IsMousePosValid(&io.MousePos) == IsMousePosValid(&io.MousePosPrev))
16711 apply_toggle_layer = true;
16712 if (!IsKeyDown(g.NavWindowingToggleKey))
16713 g.NavWindowingToggleLayer = false;
16714 }
16715
16716 // Move window
16717 if (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoMove))
16718 {
16719 ImVec2 nav_move_dir;
16720 if (g.NavInputSource == ImGuiInputSource_Keyboard && !io.KeyShift)
16721 nav_move_dir =
16722 GetKeyMagnitude2d(ImGuiKey_LeftArrow, ImGuiKey_RightArrow, ImGuiKey_UpArrow, ImGuiKey_DownArrow);
16723 if (g.NavInputSource == ImGuiInputSource_Gamepad)
16724 nav_move_dir = GetKeyMagnitude2d(ImGuiKey_GamepadLStickLeft, ImGuiKey_GamepadLStickRight,
16725 ImGuiKey_GamepadLStickUp, ImGuiKey_GamepadLStickDown);
16726 if (nav_move_dir.x != 0.0f || nav_move_dir.y != 0.0f)
16727 {
16728 const float NAV_MOVE_SPEED = 800.0f;
16729 const float move_step =
16730 NAV_MOVE_SPEED * io.DeltaTime * ImMin(io.DisplayFramebufferScale.x, io.DisplayFramebufferScale.y);
16731 g.NavWindowingAccumDeltaPos += nav_move_dir * move_step;
16732 g.NavHighlightItemUnderNav = true;
16733 ImVec2 accum_floored = ImTrunc(g.NavWindowingAccumDeltaPos);
16734 if (accum_floored.x != 0.0f || accum_floored.y != 0.0f)
16735 {
16736 ImGuiWindow *moving_window = g.NavWindowingTarget->RootWindowDockTree;
16737 SetWindowPos(moving_window, moving_window->Pos + accum_floored, ImGuiCond_Always);
16738 g.NavWindowingAccumDeltaPos -= accum_floored;
16739 }
16740 }
16741 }
16742
16743 // Apply final focus
16744 if (apply_focus_window)
16745 NavUpdateWindowingApplyFocus(apply_focus_window);
16746
16747 // Apply menu/layer toggle
16748 if (apply_toggle_layer && g.NavWindow)
16749 {
16750 ClearActiveID();
16751
16752 // Move to parent menu if necessary
16753 ImGuiWindow *new_nav_window = g.NavWindow;
16754 while (new_nav_window->ParentWindow &&
16755 (new_nav_window->DC.NavLayersActiveMask & (1 << ImGuiNavLayer_Menu)) == 0 &&
16756 (new_nav_window->Flags & ImGuiWindowFlags_ChildWindow) != 0 &&
16757 (new_nav_window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0)
16758 new_nav_window = new_nav_window->ParentWindow;
16759 if (new_nav_window != g.NavWindow)
16760 {
16761 ImGuiWindow *old_nav_window = g.NavWindow;
16762 FocusWindow(new_nav_window);
16763 new_nav_window->NavLastChildNavWindow = old_nav_window;
16764 }
16765
16766 // Toggle layer
16767 const ImGuiNavLayer new_nav_layer = (g.NavWindow->DC.NavLayersActiveMask & (1 << ImGuiNavLayer_Menu))
16768 ? (ImGuiNavLayer)((int)g.NavLayer ^ 1)
16769 : ImGuiNavLayer_Main;
16770 if (new_nav_layer != g.NavLayer)
16771 {
16772 // Reinitialize navigation when entering menu bar with the Alt key (FIXME: could be a properly of the
16773 // layer?)
16774 const bool preserve_layer_1_nav_id = (new_nav_window->DockNodeAsHost != NULL);
16775 if (new_nav_layer == ImGuiNavLayer_Menu && !preserve_layer_1_nav_id)
16776 g.NavWindow->NavLastIds[new_nav_layer] = 0;
16777 NavRestoreLayer(new_nav_layer);
16778 SetNavCursorVisibleAfterMove();
16779 }
16780 }
16781}
16782
16783// Window has already passed the IsWindowNavFocusable()
16784static const char *GetFallbackWindowNameForWindowingList(ImGuiWindow *window)
16785{
16786 if (window->Flags & ImGuiWindowFlags_Popup)
16787 return ImGui::LocalizeGetMsg(ImGuiLocKey_WindowingPopup);
16788 if ((window->Flags & ImGuiWindowFlags_MenuBar) && strcmp(window->Name, "##MainMenuBar") == 0)
16789 return ImGui::LocalizeGetMsg(ImGuiLocKey_WindowingMainMenuBar);
16790 if (window->DockNodeAsHost)
16791 return "(Dock node)"; // Not normally shown to user.
16792 return ImGui::LocalizeGetMsg(ImGuiLocKey_WindowingUntitled);
16793}
16794
16795// Overlay displayed when using CTRL+TAB. Called by EndFrame().
16796void ImGui::NavUpdateWindowingOverlay()
16797{
16798 ImGuiContext &g = *GImGui;
16799 IM_ASSERT(g.NavWindowingTarget != NULL);
16800
16801 if (g.NavWindowingTimer < NAV_WINDOWING_LIST_APPEAR_DELAY)
16802 return;
16803
16804 if (g.NavWindowingListWindow == NULL)
16805 g.NavWindowingListWindow = FindWindowByName("##NavWindowingOverlay");
16806 const ImGuiViewport *viewport = /*g.NavWindow ? g.NavWindow->Viewport :*/ GetMainViewport();
16807 SetNextWindowSizeConstraints(ImVec2(viewport->Size.x * 0.20f, viewport->Size.y * 0.20f), ImVec2(FLT_MAX, FLT_MAX));
16808 SetNextWindowPos(viewport->GetCenter(), ImGuiCond_Always, ImVec2(0.5f, 0.5f));
16809 PushStyleVar(ImGuiStyleVar_WindowPadding, g.Style.WindowPadding * 2.0f);
16810 Begin("##NavWindowingOverlay", NULL,
16811 ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoResize |
16812 ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_AlwaysAutoResize |
16813 ImGuiWindowFlags_NoSavedSettings);
16814 if (g.ContextName[0] != 0)
16815 SeparatorText(g.ContextName);
16816 for (int n = g.WindowsFocusOrder.Size - 1; n >= 0; n--)
16817 {
16818 ImGuiWindow *window = g.WindowsFocusOrder[n];
16819 IM_ASSERT(window != NULL); // Fix static analyzers
16820 if (!IsWindowNavFocusable(window))
16821 continue;
16822 const char *label = window->Name;
16823 if (label == FindRenderedTextEnd(label))
16824 label = GetFallbackWindowNameForWindowingList(window);
16825 Selectable(label, g.NavWindowingTarget == window);
16826 }
16827 End();
16828 PopStyleVar();
16829}
16830
16831//-----------------------------------------------------------------------------
16832// [SECTION] DRAG AND DROP
16833//-----------------------------------------------------------------------------
16834
16835bool ImGui::IsDragDropActive()
16836{
16837 ImGuiContext &g = *GImGui;
16838 return g.DragDropActive;
16839}
16840
16841void ImGui::ClearDragDrop()
16842{
16843 ImGuiContext &g = *GImGui;
16844 if (g.DragDropActive)
16845 IMGUI_DEBUG_LOG_ACTIVEID("[dragdrop] ClearDragDrop()\n");
16846 g.DragDropActive = false;
16847 g.DragDropPayload.Clear();
16848 g.DragDropAcceptFlags = ImGuiDragDropFlags_None;
16849 g.DragDropAcceptIdCurr = g.DragDropAcceptIdPrev = 0;
16850 g.DragDropAcceptIdCurrRectSurface = FLT_MAX;
16851 g.DragDropAcceptFrameCount = -1;
16852
16853 g.DragDropPayloadBufHeap.clear();
16854 memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal));
16855}
16856
16857bool ImGui::BeginTooltipHidden()
16858{
16859 ImGuiContext &g = *GImGui;
16860 bool ret = Begin("##Tooltip_Hidden", NULL,
16861 ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoTitleBar |
16862 ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings |
16863 ImGuiWindowFlags_AlwaysAutoResize);
16864 SetWindowHiddenAndSkipItemsForCurrentFrame(g.CurrentWindow);
16865 return ret;
16866}
16867
16868// When this returns true you need to: a) call SetDragDropPayload() exactly once, b) you may render the payload
16869// visual/description, c) call EndDragDropSource() If the item has an identifier:
16870// - This assume/require the item to be activated (typically via ButtonBehavior).
16871// - Therefore if you want to use this with a mouse button other than left mouse button, it is up to the item itself to
16872// activate with another button.
16873// - We then pull and use the mouse button that was used to activate the item and use it to carry on the drag.
16874// If the item has no identifier:
16875// - Currently always assume left mouse button.
16876bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags)
16877{
16878 ImGuiContext &g = *GImGui;
16879 ImGuiWindow *window = g.CurrentWindow;
16880
16881 // FIXME-DRAGDROP: While in the common-most "drag from non-zero active id" case we can tell the mouse button,
16882 // in both SourceExtern and id==0 cases we may requires something else (explicit flags or some heuristic).
16883 ImGuiMouseButton mouse_button = ImGuiMouseButton_Left;
16884
16885 bool source_drag_active = false;
16886 ImGuiID source_id = 0;
16887 ImGuiID source_parent_id = 0;
16888 if ((flags & ImGuiDragDropFlags_SourceExtern) == 0)
16889 {
16890 source_id = g.LastItemData.ID;
16891 if (source_id != 0)
16892 {
16893 // Common path: items with ID
16894 if (g.ActiveId != source_id)
16895 return false;
16896 if (g.ActiveIdMouseButton != -1)
16897 mouse_button = g.ActiveIdMouseButton;
16898 if (g.IO.MouseDown[mouse_button] == false || window->SkipItems)
16899 return false;
16900 g.ActiveIdAllowOverlap = false;
16901 }
16902 else
16903 {
16904 // Uncommon path: items without ID
16905 if (g.IO.MouseDown[mouse_button] == false || window->SkipItems)
16906 return false;
16907 if ((g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect) == 0 &&
16908 (g.ActiveId == 0 || g.ActiveIdWindow != window))
16909 return false;
16910
16911 // If you want to use BeginDragDropSource() on an item with no unique identifier for interaction, such as
16912 // Text() or Image(), you need to: A) Read the explanation below, B) Use the
16913 // ImGuiDragDropFlags_SourceAllowNullID flag.
16914 if (!(flags & ImGuiDragDropFlags_SourceAllowNullID))
16915 {
16916 IM_ASSERT(0);
16917 return false;
16918 }
16919
16920 // Magic fallback to handle items with no assigned ID, e.g. Text(), Image()
16921 // We build a throwaway ID based on current ID stack + relative AABB of items in window.
16922 // THE IDENTIFIER WON'T SURVIVE ANY REPOSITIONING/RESIZINGG OF THE WIDGET, so if your widget moves your
16923 // dragging operation will be canceled. We don't need to maintain/call ClearActiveID() as releasing the
16924 // button will early out this function and trigger !ActiveIdIsAlive. Rely on keeping other
16925 // window->LastItemXXX fields intact.
16926 source_id = g.LastItemData.ID = window->GetIDFromRectangle(g.LastItemData.Rect);
16927 KeepAliveID(source_id);
16928 bool is_hovered = ItemHoverable(g.LastItemData.Rect, source_id, g.LastItemData.ItemFlags);
16929 if (is_hovered && g.IO.MouseClicked[mouse_button])
16930 {
16931 SetActiveID(source_id, window);
16932 FocusWindow(window);
16933 }
16934 if (g.ActiveId == source_id) // Allow the underlying widget to display/return hovered during the mouse
16935 // release frame, else we would get a flicker.
16936 g.ActiveIdAllowOverlap = is_hovered;
16937 }
16938 if (g.ActiveId != source_id)
16939 return false;
16940 source_parent_id = window->IDStack.back();
16941 source_drag_active = IsMouseDragging(mouse_button);
16942
16943 // Disable navigation and key inputs while dragging + cancel existing request if any
16944 SetActiveIdUsingAllKeyboardKeys();
16945 }
16946 else
16947 {
16948 // When ImGuiDragDropFlags_SourceExtern is set:
16949 window = NULL;
16950 source_id = ImHashStr("#SourceExtern");
16951 source_drag_active = true;
16952 mouse_button = g.IO.MouseDown[0] ? 0 : -1;
16953 KeepAliveID(source_id);
16954 SetActiveID(source_id, NULL);
16955 }
16956
16957 IM_ASSERT(g.DragDropWithinTarget == false); // Can't nest BeginDragDropSource() and BeginDragDropTarget()
16958 if (!source_drag_active)
16959 return false;
16960
16961 // Activate drag and drop
16962 if (!g.DragDropActive)
16963 {
16964 IM_ASSERT(source_id != 0);
16965 ClearDragDrop();
16966 IMGUI_DEBUG_LOG_ACTIVEID("[dragdrop] BeginDragDropSource() DragDropActive = true, source_id = 0x%08X%s\n",
16967 source_id, (flags & ImGuiDragDropFlags_SourceExtern) ? " (EXTERN)" : "");
16968 ImGuiPayload &payload = g.DragDropPayload;
16969 payload.SourceId = source_id;
16970 payload.SourceParentId = source_parent_id;
16971 g.DragDropActive = true;
16972 g.DragDropSourceFlags = flags;
16973 g.DragDropMouseButton = mouse_button;
16974 if (payload.SourceId == g.ActiveId)
16975 g.ActiveIdNoClearOnFocusLoss = true;
16976 }
16977 g.DragDropSourceFrameCount = g.FrameCount;
16978 g.DragDropWithinSource = true;
16979
16980 if (!(flags & ImGuiDragDropFlags_SourceNoPreviewTooltip))
16981 {
16982 // Target can request the Source to not display its tooltip (we use a dedicated flag to make this request
16983 // explicit) We unfortunately can't just modify the source flags and skip the call to BeginTooltip, as caller
16984 // may be emitting contents.
16985 bool ret;
16986 if (g.DragDropAcceptIdPrev && (g.DragDropAcceptFlags & ImGuiDragDropFlags_AcceptNoPreviewTooltip))
16987 ret = BeginTooltipHidden();
16988 else
16989 ret = BeginTooltip();
16990 IM_ASSERT(ret); // FIXME-NEWBEGIN: If this ever becomes false, we need to Begin("##Hidden", NULL,
16991 // ImGuiWindowFlags_NoSavedSettings) + SetWindowHiddendAndSkipItemsForCurrentFrame().
16992 IM_UNUSED(ret);
16993 }
16994
16995 if (!(flags & ImGuiDragDropFlags_SourceNoDisableHover) && !(flags & ImGuiDragDropFlags_SourceExtern))
16996 g.LastItemData.StatusFlags &= ~ImGuiItemStatusFlags_HoveredRect;
16997
16998 return true;
16999}
17000
17001void ImGui::EndDragDropSource()
17002{
17003 ImGuiContext &g = *GImGui;
17004 IM_ASSERT(g.DragDropActive);
17005 IM_ASSERT(g.DragDropWithinSource && "Not after a BeginDragDropSource()?");
17006
17007 if (!(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoPreviewTooltip))
17008 EndTooltip();
17009
17010 // Discard the drag if have not called SetDragDropPayload()
17011 if (g.DragDropPayload.DataFrameCount == -1)
17012 ClearDragDrop();
17013 g.DragDropWithinSource = false;
17014}
17015
17016// Use 'cond' to choose to submit payload on drag start or every frame
17017bool ImGui::SetDragDropPayload(const char *type, const void *data, size_t data_size, ImGuiCond cond)
17018{
17019 ImGuiContext &g = *GImGui;
17020 ImGuiPayload &payload = g.DragDropPayload;
17021 if (cond == 0)
17022 cond = ImGuiCond_Always;
17023
17024 IM_ASSERT(type != NULL);
17025 IM_ASSERT(ImStrlen(type) < IM_ARRAYSIZE(payload.DataType) && "Payload type can be at most 32 characters long");
17026 IM_ASSERT((data != NULL && data_size > 0) || (data == NULL && data_size == 0));
17027 IM_ASSERT(cond == ImGuiCond_Always || cond == ImGuiCond_Once);
17028 IM_ASSERT(payload.SourceId != 0); // Not called between BeginDragDropSource() and EndDragDropSource()
17029
17030 if (cond == ImGuiCond_Always || payload.DataFrameCount == -1)
17031 {
17032 // Copy payload
17033 ImStrncpy(payload.DataType, type, IM_ARRAYSIZE(payload.DataType));
17034 g.DragDropPayloadBufHeap.resize(0);
17035 if (data_size > sizeof(g.DragDropPayloadBufLocal))
17036 {
17037 // Store in heap
17038 g.DragDropPayloadBufHeap.resize((int)data_size);
17039 payload.Data = g.DragDropPayloadBufHeap.Data;
17040 memcpy(payload.Data, data, data_size);
17041 }
17042 else if (data_size > 0)
17043 {
17044 // Store locally
17045 memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal));
17046 payload.Data = g.DragDropPayloadBufLocal;
17047 memcpy(payload.Data, data, data_size);
17048 }
17049 else
17050 {
17051 payload.Data = NULL;
17052 }
17053 payload.DataSize = (int)data_size;
17054 }
17055 payload.DataFrameCount = g.FrameCount;
17056
17057 // Return whether the payload has been accepted
17058 return (g.DragDropAcceptFrameCount == g.FrameCount) || (g.DragDropAcceptFrameCount == g.FrameCount - 1);
17059}
17060
17061bool ImGui::BeginDragDropTargetCustom(const ImRect &bb, ImGuiID id)
17062{
17063 ImGuiContext &g = *GImGui;
17064 if (!g.DragDropActive)
17065 return false;
17066
17067 ImGuiWindow *window = g.CurrentWindow;
17068 ImGuiWindow *hovered_window = g.HoveredWindowUnderMovingWindow;
17069 if (hovered_window == NULL || window->RootWindowDockTree != hovered_window->RootWindowDockTree)
17070 return false;
17071 IM_ASSERT(id != 0);
17072 if (!IsMouseHoveringRect(bb.Min, bb.Max) || (id == g.DragDropPayload.SourceId))
17073 return false;
17074 if (window->SkipItems)
17075 return false;
17076
17077 IM_ASSERT(g.DragDropWithinTarget == false &&
17078 g.DragDropWithinSource == false); // Can't nest BeginDragDropSource() and BeginDragDropTarget()
17079 g.DragDropTargetRect = bb;
17080 g.DragDropTargetClipRect = window->ClipRect; // May want to be overridden by user depending on use case?
17081 g.DragDropTargetId = id;
17082 g.DragDropWithinTarget = true;
17083 return true;
17084}
17085
17086// We don't use BeginDragDropTargetCustom() and duplicate its code because:
17087// 1) we use LastItemData's ImGuiItemStatusFlags_HoveredRect which handles items that push a temporarily clip rectangle
17088// in their code. Calling BeginDragDropTargetCustom(LastItemRect) would not handle them. 2) and it's faster. as this
17089// code may be very frequently called, we want to early out as fast as we can. Also note how the HoveredWindow test is
17090// positioned differently in both functions (in both functions we optimize for the cheapest early out case)
17091bool ImGui::BeginDragDropTarget()
17092{
17093 ImGuiContext &g = *GImGui;
17094 if (!g.DragDropActive)
17095 return false;
17096
17097 ImGuiWindow *window = g.CurrentWindow;
17098 if (!(g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect))
17099 return false;
17100 ImGuiWindow *hovered_window = g.HoveredWindowUnderMovingWindow;
17101 if (hovered_window == NULL || window->RootWindowDockTree != hovered_window->RootWindowDockTree || window->SkipItems)
17102 return false;
17103
17104 const ImRect &display_rect = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasDisplayRect)
17105 ? g.LastItemData.DisplayRect
17106 : g.LastItemData.Rect;
17107 ImGuiID id = g.LastItemData.ID;
17108 if (id == 0)
17109 {
17110 id = window->GetIDFromRectangle(display_rect);
17111 KeepAliveID(id);
17112 }
17113 if (g.DragDropPayload.SourceId == id)
17114 return false;
17115
17116 IM_ASSERT(g.DragDropWithinTarget == false &&
17117 g.DragDropWithinSource == false); // Can't nest BeginDragDropSource() and BeginDragDropTarget()
17118 g.DragDropTargetRect = display_rect;
17119 g.DragDropTargetClipRect =
17120 (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasClipRect) ? g.LastItemData.ClipRect : window->ClipRect;
17121 g.DragDropTargetId = id;
17122 g.DragDropWithinTarget = true;
17123 return true;
17124}
17125
17126bool ImGui::IsDragDropPayloadBeingAccepted()
17127{
17128 ImGuiContext &g = *GImGui;
17129 return g.DragDropActive && g.DragDropAcceptIdPrev != 0;
17130}
17131
17132const ImGuiPayload *ImGui::AcceptDragDropPayload(const char *type, ImGuiDragDropFlags flags)
17133{
17134 ImGuiContext &g = *GImGui;
17135 ImGuiPayload &payload = g.DragDropPayload;
17136 IM_ASSERT(g.DragDropActive); // Not called between BeginDragDropTarget() and EndDragDropTarget() ?
17137 IM_ASSERT(payload.DataFrameCount != -1); // Forgot to call EndDragDropTarget() ?
17138 if (type != NULL && !payload.IsDataType(type))
17139 return NULL;
17140
17141 // Accept smallest drag target bounding box, this allows us to nest drag targets conveniently without ordering
17142 // constraints. NB: We currently accept NULL id as target. However, overlapping targets requires a unique ID to
17143 // function!
17144 const bool was_accepted_previously = (g.DragDropAcceptIdPrev == g.DragDropTargetId);
17145 ImRect r = g.DragDropTargetRect;
17146 float r_surface = r.GetWidth() * r.GetHeight();
17147 if (r_surface > g.DragDropAcceptIdCurrRectSurface)
17148 return NULL;
17149
17150 g.DragDropAcceptFlags = flags;
17151 g.DragDropAcceptIdCurr = g.DragDropTargetId;
17152 g.DragDropAcceptIdCurrRectSurface = r_surface;
17153 // IMGUI_DEBUG_LOG("AcceptDragDropPayload(): %08X: accept\n", g.DragDropTargetId);
17154
17155 // Render default drop visuals
17156 payload.Preview = was_accepted_previously;
17157 flags |= (g.DragDropSourceFlags &
17158 ImGuiDragDropFlags_AcceptNoDrawDefaultRect); // Source can also inhibit the preview (useful for external
17159 // sources that live for 1 frame)
17160 if (!(flags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect) && payload.Preview)
17161 RenderDragDropTargetRect(r, g.DragDropTargetClipRect);
17162
17163 g.DragDropAcceptFrameCount = g.FrameCount;
17164 if ((g.DragDropSourceFlags & ImGuiDragDropFlags_SourceExtern) && g.DragDropMouseButton == -1)
17165 payload.Delivery = was_accepted_previously && (g.DragDropSourceFrameCount < g.FrameCount);
17166 else
17167 payload.Delivery =
17168 was_accepted_previously &&
17169 !IsMouseDown(g.DragDropMouseButton); // For extern drag sources affecting OS window focus, it's easier to
17170 // just test !IsMouseDown() instead of IsMouseReleased()
17171 if (!payload.Delivery && !(flags & ImGuiDragDropFlags_AcceptBeforeDelivery))
17172 return NULL;
17173
17174 if (payload.Delivery)
17175 IMGUI_DEBUG_LOG_ACTIVEID("[dragdrop] AcceptDragDropPayload(): 0x%08X: payload delivery\n", g.DragDropTargetId);
17176 return &payload;
17177}
17178
17179// FIXME-STYLE FIXME-DRAGDROP: Settle on a proper default visuals for drop target.
17180void ImGui::RenderDragDropTargetRect(const ImRect &bb, const ImRect &item_clip_rect)
17181{
17182 ImGuiContext &g = *GImGui;
17183 ImGuiWindow *window = g.CurrentWindow;
17184 ImRect bb_display = bb;
17185 bb_display.ClipWith(
17186 item_clip_rect); // Clip THEN expand so we have a way to visualize that target is not entirely visible.
17187 bb_display.Expand(3.5f);
17188 bool push_clip_rect = !window->ClipRect.Contains(bb_display);
17189 if (push_clip_rect)
17190 window->DrawList->PushClipRectFullScreen();
17191 window->DrawList->AddRect(bb_display.Min, bb_display.Max, GetColorU32(ImGuiCol_DragDropTarget), 0.0f, 0,
17192 2.0f); // FIXME-DPI
17193 if (push_clip_rect)
17194 window->DrawList->PopClipRect();
17195}
17196
17197const ImGuiPayload *ImGui::GetDragDropPayload()
17198{
17199 ImGuiContext &g = *GImGui;
17200 return (g.DragDropActive && g.DragDropPayload.DataFrameCount != -1) ? &g.DragDropPayload : NULL;
17201}
17202
17203void ImGui::EndDragDropTarget()
17204{
17205 ImGuiContext &g = *GImGui;
17206 IM_ASSERT(g.DragDropActive);
17207 IM_ASSERT(g.DragDropWithinTarget);
17208 g.DragDropWithinTarget = false;
17209
17210 // Clear drag and drop state payload right after delivery
17211 if (g.DragDropPayload.Delivery)
17212 ClearDragDrop();
17213}
17214
17215//-----------------------------------------------------------------------------
17216// [SECTION] LOGGING/CAPTURING
17217//-----------------------------------------------------------------------------
17218// All text output from the interface can be captured into tty/file/clipboard.
17219// By default, tree nodes are automatically opened during logging.
17220//-----------------------------------------------------------------------------
17221
17222// Pass text data straight to log (without being displayed)
17223static inline void LogTextV(ImGuiContext &g, const char *fmt, va_list args)
17224{
17225 if (g.LogFile)
17226 {
17227 g.LogBuffer.Buf.resize(0);
17228 g.LogBuffer.appendfv(fmt, args);
17229 ImFileWrite(g.LogBuffer.c_str(), sizeof(char), (ImU64)g.LogBuffer.size(), g.LogFile);
17230 }
17231 else
17232 {
17233 g.LogBuffer.appendfv(fmt, args);
17234 }
17235}
17236
17237void ImGui::LogText(const char *fmt, ...)
17238{
17239 ImGuiContext &g = *GImGui;
17240 if (!g.LogEnabled)
17241 return;
17242
17243 va_list args;
17244 va_start(args, fmt);
17245 LogTextV(g, fmt, args);
17246 va_end(args);
17247}
17248
17249void ImGui::LogTextV(const char *fmt, va_list args)
17250{
17251 ImGuiContext &g = *GImGui;
17252 if (!g.LogEnabled)
17253 return;
17254
17255 LogTextV(g, fmt, args);
17256}
17257
17258// Internal version that takes a position to decide on newline placement and pad items according to their depth.
17259// We split text into individual lines to add current tree level padding
17260// FIXME: This code is a little complicated perhaps, considering simplifying the whole system.
17261void ImGui::LogRenderedText(const ImVec2 *ref_pos, const char *text, const char *text_end)
17262{
17263 ImGuiContext &g = *GImGui;
17264 ImGuiWindow *window = g.CurrentWindow;
17265
17266 const char *prefix = g.LogNextPrefix;
17267 const char *suffix = g.LogNextSuffix;
17268 g.LogNextPrefix = g.LogNextSuffix = NULL;
17269
17270 if (!text_end)
17271 text_end = FindRenderedTextEnd(text, text_end);
17272
17273 const bool log_new_line = ref_pos && (ref_pos->y > g.LogLinePosY + g.Style.FramePadding.y + 1);
17274 if (ref_pos)
17275 g.LogLinePosY = ref_pos->y;
17276 if (log_new_line)
17277 {
17278 LogText(IM_NEWLINE);
17279 g.LogLineFirstItem = true;
17280 }
17281
17282 if (prefix)
17283 LogRenderedText(ref_pos, prefix,
17284 prefix + ImStrlen(prefix)); // Calculate end ourself to ensure "##" are included here.
17285
17286 // Re-adjust padding if we have popped out of our starting depth
17287 if (g.LogDepthRef > window->DC.TreeDepth)
17288 g.LogDepthRef = window->DC.TreeDepth;
17289 const int tree_depth = (window->DC.TreeDepth - g.LogDepthRef);
17290
17291 const char *text_remaining = text;
17292 for (;;)
17293 {
17294 // Split the string. Each new line (after a '\n') is followed by indentation corresponding to the current depth
17295 // of our log entry. We don't add a trailing \n yet to allow a subsequent item on the same line to be captured.
17296 const char *line_start = text_remaining;
17297 const char *line_end = ImStreolRange(line_start, text_end);
17298 const bool is_last_line = (line_end == text_end);
17299 if (line_start != line_end || !is_last_line)
17300 {
17301 const int line_length = (int)(line_end - line_start);
17302 const int indentation = g.LogLineFirstItem ? tree_depth * 4 : 1;
17303 LogText("%*s%.*s", indentation, "", line_length, line_start);
17304 g.LogLineFirstItem = false;
17305 if (*line_end == '\n')
17306 {
17307 LogText(IM_NEWLINE);
17308 g.LogLineFirstItem = true;
17309 }
17310 }
17311 if (is_last_line)
17312 break;
17313 text_remaining = line_end + 1;
17314 }
17315
17316 if (suffix)
17317 LogRenderedText(ref_pos, suffix, suffix + ImStrlen(suffix));
17318}
17319
17320// Start logging/capturing text output
17321void ImGui::LogBegin(ImGuiLogFlags flags, int auto_open_depth)
17322{
17323 ImGuiContext &g = *GImGui;
17324 ImGuiWindow *window = g.CurrentWindow;
17325 IM_ASSERT(g.LogEnabled == false);
17326 IM_ASSERT(g.LogFile == NULL && g.LogBuffer.empty());
17327 IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiLogFlags_OutputMask_)); // Check that only 1 type flag is used
17328
17329 g.LogEnabled = g.ItemUnclipByLog = true;
17330 g.LogFlags = flags;
17331 g.LogWindow = window;
17332 g.LogNextPrefix = g.LogNextSuffix = NULL;
17333 g.LogDepthRef = window->DC.TreeDepth;
17334 g.LogDepthToExpand = ((auto_open_depth >= 0) ? auto_open_depth : g.LogDepthToExpandDefault);
17335 g.LogLinePosY = FLT_MAX;
17336 g.LogLineFirstItem = true;
17337}
17338
17339// Important: doesn't copy underlying data, use carefully (prefix/suffix must be in scope at the time of the next
17340// LogRenderedText)
17341void ImGui::LogSetNextTextDecoration(const char *prefix, const char *suffix)
17342{
17343 ImGuiContext &g = *GImGui;
17344 g.LogNextPrefix = prefix;
17345 g.LogNextSuffix = suffix;
17346}
17347
17348void ImGui::LogToTTY(int auto_open_depth)
17349{
17350 ImGuiContext &g = *GImGui;
17351 if (g.LogEnabled)
17352 return;
17353 IM_UNUSED(auto_open_depth);
17354#ifndef IMGUI_DISABLE_TTY_FUNCTIONS
17355 LogBegin(ImGuiLogFlags_OutputTTY, auto_open_depth);
17356 g.LogFile = stdout;
17357#endif
17358}
17359
17360// Start logging/capturing text output to given file
17361void ImGui::LogToFile(int auto_open_depth, const char *filename)
17362{
17363 ImGuiContext &g = *GImGui;
17364 if (g.LogEnabled)
17365 return;
17366
17367 // FIXME: We could probably open the file in text mode "at", however note that clipboard/buffer logging will still
17368 // be subject to outputting OS-incompatible carriage return if within strings the user doesn't use IM_NEWLINE.
17369 // By opening the file in binary mode "ab" we have consistent output everywhere.
17370 if (!filename)
17371 filename = g.IO.LogFilename;
17372 if (!filename || !filename[0])
17373 return;
17374 ImFileHandle f = ImFileOpen(filename, "ab");
17375 if (!f)
17376 {
17377 IM_ASSERT(0);
17378 return;
17379 }
17380
17381 LogBegin(ImGuiLogFlags_OutputFile, auto_open_depth);
17382 g.LogFile = f;
17383}
17384
17385// Start logging/capturing text output to clipboard
17386void ImGui::LogToClipboard(int auto_open_depth)
17387{
17388 ImGuiContext &g = *GImGui;
17389 if (g.LogEnabled)
17390 return;
17391 LogBegin(ImGuiLogFlags_OutputClipboard, auto_open_depth);
17392}
17393
17394void ImGui::LogToBuffer(int auto_open_depth)
17395{
17396 ImGuiContext &g = *GImGui;
17397 if (g.LogEnabled)
17398 return;
17399 LogBegin(ImGuiLogFlags_OutputBuffer, auto_open_depth);
17400}
17401
17402void ImGui::LogFinish()
17403{
17404 ImGuiContext &g = *GImGui;
17405 if (!g.LogEnabled)
17406 return;
17407
17408 LogText(IM_NEWLINE);
17409 switch (g.LogFlags & ImGuiLogFlags_OutputMask_)
17410 {
17411 case ImGuiLogFlags_OutputTTY:
17412#ifndef IMGUI_DISABLE_TTY_FUNCTIONS
17413 fflush(g.LogFile);
17414#endif
17415 break;
17416 case ImGuiLogFlags_OutputFile:
17417 ImFileClose(g.LogFile);
17418 break;
17419 case ImGuiLogFlags_OutputBuffer:
17420 break;
17421 case ImGuiLogFlags_OutputClipboard:
17422 if (!g.LogBuffer.empty())
17423 SetClipboardText(g.LogBuffer.begin());
17424 break;
17425 default:
17426 IM_ASSERT(0);
17427 break;
17428 }
17429
17430 g.LogEnabled = g.ItemUnclipByLog = false;
17431 g.LogFlags = ImGuiLogFlags_None;
17432 g.LogFile = NULL;
17433 g.LogBuffer.clear();
17434}
17435
17436// Helper to display logging buttons
17437// FIXME-OBSOLETE: We should probably obsolete this and let the user have their own helper (this is one of the oldest
17438// function alive!)
17439void ImGui::LogButtons()
17440{
17441 ImGuiContext &g = *GImGui;
17442
17443 PushID("LogButtons");
17444#ifndef IMGUI_DISABLE_TTY_FUNCTIONS
17445 const bool log_to_tty = Button("Log To TTY");
17446 SameLine();
17447#else
17448 const bool log_to_tty = false;
17449#endif
17450 const bool log_to_file = Button("Log To File");
17451 SameLine();
17452 const bool log_to_clipboard = Button("Log To Clipboard");
17453 SameLine();
17454 PushItemFlag(ImGuiItemFlags_NoTabStop, true);
17455 SetNextItemWidth(80.0f);
17456 SliderInt("Default Depth", &g.LogDepthToExpandDefault, 0, 9, NULL);
17457 PopItemFlag();
17458 PopID();
17459
17460 // Start logging at the end of the function so that the buttons don't appear in the log
17461 if (log_to_tty)
17462 LogToTTY();
17463 if (log_to_file)
17464 LogToFile();
17465 if (log_to_clipboard)
17466 LogToClipboard();
17467}
17468
17469//-----------------------------------------------------------------------------
17470// [SECTION] SETTINGS
17471//-----------------------------------------------------------------------------
17472// - UpdateSettings() [Internal]
17473// - MarkIniSettingsDirty() [Internal]
17474// - FindSettingsHandler() [Internal]
17475// - ClearIniSettings() [Internal]
17476// - LoadIniSettingsFromDisk()
17477// - LoadIniSettingsFromMemory()
17478// - SaveIniSettingsToDisk()
17479// - SaveIniSettingsToMemory()
17480//-----------------------------------------------------------------------------
17481// - CreateNewWindowSettings() [Internal]
17482// - FindWindowSettingsByID() [Internal]
17483// - FindWindowSettingsByWindow() [Internal]
17484// - ClearWindowSettings() [Internal]
17485// - WindowSettingsHandler_***() [Internal]
17486//-----------------------------------------------------------------------------
17487
17488// Called by NewFrame()
17489void ImGui::UpdateSettings()
17490{
17491 // Load settings on first frame (if not explicitly loaded manually before)
17492 ImGuiContext &g = *GImGui;
17493 if (!g.SettingsLoaded)
17494 {
17495 IM_ASSERT(g.SettingsWindows.empty());
17496 if (g.IO.IniFilename)
17497 LoadIniSettingsFromDisk(g.IO.IniFilename);
17498 g.SettingsLoaded = true;
17499 }
17500
17501 // Save settings (with a delay after the last modification, so we don't spam disk too much)
17502 if (g.SettingsDirtyTimer > 0.0f)
17503 {
17504 g.SettingsDirtyTimer -= g.IO.DeltaTime;
17505 if (g.SettingsDirtyTimer <= 0.0f)
17506 {
17507 if (g.IO.IniFilename != NULL)
17508 SaveIniSettingsToDisk(g.IO.IniFilename);
17509 else
17510 g.IO.WantSaveIniSettings = true; // Let user know they can call SaveIniSettingsToMemory(). user will
17511 // need to clear io.WantSaveIniSettings themselves.
17512 g.SettingsDirtyTimer = 0.0f;
17513 }
17514 }
17515}
17516
17517void ImGui::MarkIniSettingsDirty()
17518{
17519 ImGuiContext &g = *GImGui;
17520 if (g.SettingsDirtyTimer <= 0.0f)
17521 g.SettingsDirtyTimer = g.IO.IniSavingRate;
17522}
17523
17524void ImGui::MarkIniSettingsDirty(ImGuiWindow *window)
17525{
17526 ImGuiContext &g = *GImGui;
17527 if (!(window->Flags & ImGuiWindowFlags_NoSavedSettings))
17528 if (g.SettingsDirtyTimer <= 0.0f)
17529 g.SettingsDirtyTimer = g.IO.IniSavingRate;
17530}
17531
17532void ImGui::AddSettingsHandler(const ImGuiSettingsHandler *handler)
17533{
17534 ImGuiContext &g = *GImGui;
17535 IM_ASSERT(FindSettingsHandler(handler->TypeName) == NULL);
17536 g.SettingsHandlers.push_back(*handler);
17537}
17538
17539void ImGui::RemoveSettingsHandler(const char *type_name)
17540{
17541 ImGuiContext &g = *GImGui;
17542 if (ImGuiSettingsHandler *handler = FindSettingsHandler(type_name))
17543 g.SettingsHandlers.erase(handler);
17544}
17545
17546ImGuiSettingsHandler *ImGui::FindSettingsHandler(const char *type_name)
17547{
17548 ImGuiContext &g = *GImGui;
17549 const ImGuiID type_hash = ImHashStr(type_name);
17550 for (ImGuiSettingsHandler &handler : g.SettingsHandlers)
17551 if (handler.TypeHash == type_hash)
17552 return &handler;
17553 return NULL;
17554}
17555
17556// Clear all settings (windows, tables, docking etc.)
17557void ImGui::ClearIniSettings()
17558{
17559 ImGuiContext &g = *GImGui;
17560 g.SettingsIniData.clear();
17561 for (ImGuiSettingsHandler &handler : g.SettingsHandlers)
17562 if (handler.ClearAllFn != NULL)
17563 handler.ClearAllFn(&g, &handler);
17564}
17565
17566void ImGui::LoadIniSettingsFromDisk(const char *ini_filename)
17567{
17568 size_t file_data_size = 0;
17569 char *file_data = (char *)ImFileLoadToMemory(ini_filename, "rb", &file_data_size);
17570 if (!file_data)
17571 return;
17572 if (file_data_size > 0)
17573 LoadIniSettingsFromMemory(file_data, (size_t)file_data_size);
17574 IM_FREE(file_data);
17575}
17576
17577// Zero-tolerance, no error reporting, cheap .ini parsing
17578// Set ini_size==0 to let us use strlen(ini_data). Do not call this function with a 0 if your buffer is actually empty!
17579void ImGui::LoadIniSettingsFromMemory(const char *ini_data, size_t ini_size)
17580{
17581 ImGuiContext &g = *GImGui;
17582 IM_ASSERT(g.Initialized);
17583 // IM_ASSERT(!g.WithinFrameScope && "Cannot be called between NewFrame() and EndFrame()");
17584 // IM_ASSERT(g.SettingsLoaded == false && g.FrameCount == 0);
17585
17586 // For user convenience, we allow passing a non zero-terminated string (hence the ini_size parameter).
17587 // For our convenience and to make the code simpler, we'll also write zero-terminators within the buffer. So let's
17588 // create a writable copy..
17589 if (ini_size == 0)
17590 ini_size = ImStrlen(ini_data);
17591 g.SettingsIniData.Buf.resize((int)ini_size + 1);
17592 char *const buf = g.SettingsIniData.Buf.Data;
17593 char *const buf_end = buf + ini_size;
17594 memcpy(buf, ini_data, ini_size);
17595 buf_end[0] = 0;
17596
17597 // Call pre-read handlers
17598 // Some types will clear their data (e.g. dock information) some types will allow merge/override (window)
17599 for (ImGuiSettingsHandler &handler : g.SettingsHandlers)
17600 if (handler.ReadInitFn != NULL)
17601 handler.ReadInitFn(&g, &handler);
17602
17603 void *entry_data = NULL;
17604 ImGuiSettingsHandler *entry_handler = NULL;
17605
17606 char *line_end = NULL;
17607 for (char *line = buf; line < buf_end; line = line_end + 1)
17608 {
17609 // Skip new lines markers, then find end of the line
17610 while (*line == '\n' || *line == '\r')
17611 line++;
17612 line_end = line;
17613 while (line_end < buf_end && *line_end != '\n' && *line_end != '\r')
17614 line_end++;
17615 line_end[0] = 0;
17616 if (line[0] == ';')
17617 continue;
17618 if (line[0] == '[' && line_end > line && line_end[-1] == ']')
17619 {
17620 // Parse "[Type][Name]". Note that 'Name' can itself contains [] characters, which is acceptable with the
17621 // current format and parsing code.
17622 line_end[-1] = 0;
17623 const char *name_end = line_end - 1;
17624 const char *type_start = line + 1;
17625 char *type_end = (char *)(void *)ImStrchrRange(type_start, name_end, ']');
17626 const char *name_start = type_end ? ImStrchrRange(type_end + 1, name_end, '[') : NULL;
17627 if (!type_end || !name_start)
17628 continue;
17629 *type_end = 0; // Overwrite first ']'
17630 name_start++; // Skip second '['
17631 entry_handler = FindSettingsHandler(type_start);
17632 entry_data = entry_handler ? entry_handler->ReadOpenFn(&g, entry_handler, name_start) : NULL;
17633 }
17634 else if (entry_handler != NULL && entry_data != NULL)
17635 {
17636 // Let type handler parse the line
17637 entry_handler->ReadLineFn(&g, entry_handler, entry_data, line);
17638 }
17639 }
17640 g.SettingsLoaded = true;
17641
17642 // [DEBUG] Restore untouched copy so it can be browsed in Metrics (not strictly necessary)
17643 memcpy(buf, ini_data, ini_size);
17644
17645 // Call post-read handlers
17646 for (ImGuiSettingsHandler &handler : g.SettingsHandlers)
17647 if (handler.ApplyAllFn != NULL)
17648 handler.ApplyAllFn(&g, &handler);
17649}
17650
17651void ImGui::SaveIniSettingsToDisk(const char *ini_filename)
17652{
17653 ImGuiContext &g = *GImGui;
17654 g.SettingsDirtyTimer = 0.0f;
17655 if (!ini_filename)
17656 return;
17657
17658 size_t ini_data_size = 0;
17659 const char *ini_data = SaveIniSettingsToMemory(&ini_data_size);
17660 ImFileHandle f = ImFileOpen(ini_filename, "wt");
17661 if (!f)
17662 return;
17663 ImFileWrite(ini_data, sizeof(char), ini_data_size, f);
17664 ImFileClose(f);
17665}
17666
17667// Call registered handlers (e.g. SettingsHandlerWindow_WriteAll() + custom handlers) to write their stuff into a text
17668// buffer
17669const char *ImGui::SaveIniSettingsToMemory(size_t *out_size)
17670{
17671 ImGuiContext &g = *GImGui;
17672 g.SettingsDirtyTimer = 0.0f;
17673 g.SettingsIniData.Buf.resize(0);
17674 g.SettingsIniData.Buf.push_back(0);
17675 for (ImGuiSettingsHandler &handler : g.SettingsHandlers)
17676 handler.WriteAllFn(&g, &handler, &g.SettingsIniData);
17677 if (out_size)
17678 *out_size = (size_t)g.SettingsIniData.size();
17679 return g.SettingsIniData.c_str();
17680}
17681
17682ImGuiWindowSettings *ImGui::CreateNewWindowSettings(const char *name)
17683{
17684 ImGuiContext &g = *GImGui;
17685
17686 if (g.IO.ConfigDebugIniSettings == false)
17687 {
17688 // Skip to the "###" marker if any. We don't skip past to match the behavior of GetID()
17689 // Preserve the full string when ConfigDebugVerboseIniSettings is set to make .ini inspection easier.
17690 if (const char *p = strstr(name, "###"))
17691 name = p;
17692 }
17693 const size_t name_len = ImStrlen(name);
17694
17695 // Allocate chunk
17696 const size_t chunk_size = sizeof(ImGuiWindowSettings) + name_len + 1;
17697 ImGuiWindowSettings *settings = g.SettingsWindows.alloc_chunk(chunk_size);
17698 IM_PLACEMENT_NEW(settings) ImGuiWindowSettings();
17699 settings->ID = ImHashStr(name, name_len);
17700 memcpy(settings->GetName(), name, name_len + 1); // Store with zero terminator
17701
17702 return settings;
17703}
17704
17705// We don't provide a FindWindowSettingsByName() because Docking system doesn't always hold on names.
17706// This is called once per window .ini entry + once per newly instantiated window.
17707ImGuiWindowSettings *ImGui::FindWindowSettingsByID(ImGuiID id)
17708{
17709 ImGuiContext &g = *GImGui;
17710 for (ImGuiWindowSettings *settings = g.SettingsWindows.begin(); settings != NULL;
17711 settings = g.SettingsWindows.next_chunk(settings))
17712 if (settings->ID == id && !settings->WantDelete)
17713 return settings;
17714 return NULL;
17715}
17716
17717// This is faster if you are holding on a Window already as we don't need to perform a search.
17718ImGuiWindowSettings *ImGui::FindWindowSettingsByWindow(ImGuiWindow *window)
17719{
17720 ImGuiContext &g = *GImGui;
17721 if (window->SettingsOffset != -1)
17722 return g.SettingsWindows.ptr_from_offset(window->SettingsOffset);
17723 return FindWindowSettingsByID(window->ID);
17724}
17725
17726// This will revert window to its initial state, including enabling the ImGuiCond_FirstUseEver/ImGuiCond_Once conditions
17727// once more.
17728void ImGui::ClearWindowSettings(const char *name)
17729{
17730 // IMGUI_DEBUG_LOG("ClearWindowSettings('%s')\n", name);
17731 ImGuiContext &g = *GImGui;
17732 ImGuiWindow *window = FindWindowByName(name);
17733 if (window != NULL)
17734 {
17735 window->Flags |= ImGuiWindowFlags_NoSavedSettings;
17736 InitOrLoadWindowSettings(window, NULL);
17737 if (window->DockId != 0)
17738 DockContextProcessUndockWindow(&g, window, true);
17739 }
17740 if (ImGuiWindowSettings *settings =
17741 window ? FindWindowSettingsByWindow(window) : FindWindowSettingsByID(ImHashStr(name)))
17742 settings->WantDelete = true;
17743}
17744
17745static void WindowSettingsHandler_ClearAll(ImGuiContext *ctx, ImGuiSettingsHandler *)
17746{
17747 ImGuiContext &g = *ctx;
17748 for (ImGuiWindow *window : g.Windows)
17749 window->SettingsOffset = -1;
17750 g.SettingsWindows.clear();
17751}
17752
17753static void *WindowSettingsHandler_ReadOpen(ImGuiContext *, ImGuiSettingsHandler *, const char *name)
17754{
17755 ImGuiID id = ImHashStr(name);
17756 ImGuiWindowSettings *settings = ImGui::FindWindowSettingsByID(id);
17757 if (settings)
17758 *settings = ImGuiWindowSettings(); // Clear existing if recycling previous entry
17759 else
17760 settings = ImGui::CreateNewWindowSettings(name);
17761 settings->ID = id;
17762 settings->WantApply = true;
17763 return (void *)settings;
17764}
17765
17766static void WindowSettingsHandler_ReadLine(ImGuiContext *, ImGuiSettingsHandler *, void *entry, const char *line)
17767{
17768 ImGuiWindowSettings *settings = (ImGuiWindowSettings *)entry;
17769 int x, y;
17770 int i;
17771 ImU32 u1;
17772 if (sscanf(line, "Pos=%i,%i", &x, &y) == 2)
17773 {
17774 settings->Pos = ImVec2ih((short)x, (short)y);
17775 }
17776 else if (sscanf(line, "Size=%i,%i", &x, &y) == 2)
17777 {
17778 settings->Size = ImVec2ih((short)x, (short)y);
17779 }
17780 else if (sscanf(line, "ViewportId=0x%08X", &u1) == 1)
17781 {
17782 settings->ViewportId = u1;
17783 }
17784 else if (sscanf(line, "ViewportPos=%i,%i", &x, &y) == 2)
17785 {
17786 settings->ViewportPos = ImVec2ih((short)x, (short)y);
17787 }
17788 else if (sscanf(line, "Collapsed=%d", &i) == 1)
17789 {
17790 settings->Collapsed = (i != 0);
17791 }
17792 else if (sscanf(line, "IsChild=%d", &i) == 1)
17793 {
17794 settings->IsChild = (i != 0);
17795 }
17796 else if (sscanf(line, "DockId=0x%X,%d", &u1, &i) == 2)
17797 {
17798 settings->DockId = u1;
17799 settings->DockOrder = (short)i;
17800 }
17801 else if (sscanf(line, "DockId=0x%X", &u1) == 1)
17802 {
17803 settings->DockId = u1;
17804 settings->DockOrder = -1;
17805 }
17806 else if (sscanf(line, "ClassId=0x%X", &u1) == 1)
17807 {
17808 settings->ClassId = u1;
17809 }
17810}
17811
17812// Apply to existing windows (if any)
17813static void WindowSettingsHandler_ApplyAll(ImGuiContext *ctx, ImGuiSettingsHandler *)
17814{
17815 ImGuiContext &g = *ctx;
17816 for (ImGuiWindowSettings *settings = g.SettingsWindows.begin(); settings != NULL;
17817 settings = g.SettingsWindows.next_chunk(settings))
17818 if (settings->WantApply)
17819 {
17820 if (ImGuiWindow *window = ImGui::FindWindowByID(settings->ID))
17821 ApplyWindowSettings(window, settings);
17822 settings->WantApply = false;
17823 }
17824}
17825
17826static void WindowSettingsHandler_WriteAll(ImGuiContext *ctx, ImGuiSettingsHandler *handler, ImGuiTextBuffer *buf)
17827{
17828 // Gather data from windows that were active during this session
17829 // (if a window wasn't opened in this session we preserve its settings)
17830 ImGuiContext &g = *ctx;
17831 for (ImGuiWindow *window : g.Windows)
17832 {
17833 if (window->Flags & ImGuiWindowFlags_NoSavedSettings)
17834 continue;
17835
17836 ImGuiWindowSettings *settings = ImGui::FindWindowSettingsByWindow(window);
17837 if (!settings)
17838 {
17839 settings = ImGui::CreateNewWindowSettings(window->Name);
17840 window->SettingsOffset = g.SettingsWindows.offset_from_ptr(settings);
17841 }
17842 IM_ASSERT(settings->ID == window->ID);
17843 settings->Pos = ImVec2ih(window->Pos - window->ViewportPos);
17844 settings->Size = ImVec2ih(window->SizeFull);
17845 settings->ViewportId = window->ViewportId;
17846 settings->ViewportPos = ImVec2ih(window->ViewportPos);
17847 IM_ASSERT(window->DockNode == NULL || window->DockNode->ID == window->DockId);
17848 settings->DockId = window->DockId;
17849 settings->ClassId = window->WindowClass.ClassId;
17850 settings->DockOrder = window->DockOrder;
17851 settings->Collapsed = window->Collapsed;
17852 settings->IsChild =
17853 (window->RootWindow !=
17854 window); // Cannot rely on ImGuiWindowFlags_ChildWindow here as docked windows have this set.
17855 settings->WantDelete = false;
17856 }
17857
17858 // Write to text buffer
17859 buf->reserve(buf->size() + g.SettingsWindows.size() * 6); // ballpark reserve
17860 for (ImGuiWindowSettings *settings = g.SettingsWindows.begin(); settings != NULL;
17861 settings = g.SettingsWindows.next_chunk(settings))
17862 {
17863 if (settings->WantDelete)
17864 continue;
17865 const char *settings_name = settings->GetName();
17866 buf->appendf("[%s][%s]\n", handler->TypeName, settings_name);
17867 if (settings->IsChild)
17868 {
17869 buf->appendf("IsChild=1\n");
17870 buf->appendf("Size=%d,%d\n", settings->Size.x, settings->Size.y);
17871 }
17872 else
17873 {
17874 if (settings->ViewportId != 0 && settings->ViewportId != ImGui::IMGUI_VIEWPORT_DEFAULT_ID)
17875 {
17876 buf->appendf("ViewportPos=%d,%d\n", settings->ViewportPos.x, settings->ViewportPos.y);
17877 buf->appendf("ViewportId=0x%08X\n", settings->ViewportId);
17878 }
17879 if (settings->Pos.x != 0 || settings->Pos.y != 0 ||
17880 settings->ViewportId == ImGui::IMGUI_VIEWPORT_DEFAULT_ID)
17881 buf->appendf("Pos=%d,%d\n", settings->Pos.x, settings->Pos.y);
17882 if (settings->Size.x != 0 || settings->Size.y != 0)
17883 buf->appendf("Size=%d,%d\n", settings->Size.x, settings->Size.y);
17884 buf->appendf("Collapsed=%d\n", settings->Collapsed);
17885 if (settings->DockId != 0)
17886 {
17887 // buf->appendf("TabId=0x%08X\n", ImHashStr("#TAB", 4, settings->ID)); // window->TabId: this is not
17888 // read back but writing it makes "debugging" the .ini data easier.
17889 if (settings->DockOrder == -1)
17890 buf->appendf("DockId=0x%08X\n", settings->DockId);
17891 else
17892 buf->appendf("DockId=0x%08X,%d\n", settings->DockId, settings->DockOrder);
17893 if (settings->ClassId != 0)
17894 buf->appendf("ClassId=0x%08X\n", settings->ClassId);
17895 }
17896 }
17897 buf->append("\n");
17898 }
17899}
17900
17901//-----------------------------------------------------------------------------
17902// [SECTION] LOCALIZATION
17903//-----------------------------------------------------------------------------
17904
17905void ImGui::LocalizeRegisterEntries(const ImGuiLocEntry *entries, int count)
17906{
17907 ImGuiContext &g = *GImGui;
17908 for (int n = 0; n < count; n++)
17909 g.LocalizationTable[entries[n].Key] = entries[n].Text;
17910}
17911
17912//-----------------------------------------------------------------------------
17913// [SECTION] VIEWPORTS, PLATFORM WINDOWS
17914//-----------------------------------------------------------------------------
17915// - GetMainViewport()
17916// - FindViewportByID()
17917// - FindViewportByPlatformHandle()
17918// - SetCurrentViewport() [Internal]
17919// - SetWindowViewport() [Internal]
17920// - GetWindowAlwaysWantOwnViewport() [Internal]
17921// - UpdateTryMergeWindowIntoHostViewport() [Internal]
17922// - UpdateTryMergeWindowIntoHostViewports() [Internal]
17923// - TranslateWindowsInViewport() [Internal]
17924// - ScaleWindowsInViewport() [Internal]
17925// - FindHoveredViewportFromPlatformWindowStack() [Internal]
17926// - UpdateViewportsNewFrame() [Internal]
17927// - UpdateViewportsEndFrame() [Internal]
17928// - AddUpdateViewport() [Internal]
17929// - WindowSelectViewport() [Internal]
17930// - WindowSyncOwnedViewport() [Internal]
17931// - UpdatePlatformWindows()
17932// - RenderPlatformWindowsDefault()
17933// - FindPlatformMonitorForPos() [Internal]
17934// - FindPlatformMonitorForRect() [Internal]
17935// - UpdateViewportPlatformMonitor() [Internal]
17936// - DestroyPlatformWindow() [Internal]
17937// - DestroyPlatformWindows()
17938//-----------------------------------------------------------------------------
17939
17940ImGuiViewport *ImGui::GetMainViewport()
17941{
17942 ImGuiContext &g = *GImGui;
17943 return g.Viewports[0];
17944}
17945
17946// FIXME: This leaks access to viewports not listed in PlatformIO.Viewports[]. Problematic? (#4236)
17947ImGuiViewport *ImGui::FindViewportByID(ImGuiID id)
17948{
17949 ImGuiContext &g = *GImGui;
17950 for (ImGuiViewportP *viewport : g.Viewports)
17951 if (viewport->ID == id)
17952 return viewport;
17953 return NULL;
17954}
17955
17956ImGuiViewport *ImGui::FindViewportByPlatformHandle(void *platform_handle)
17957{
17958 ImGuiContext &g = *GImGui;
17959 for (ImGuiViewportP *viewport : g.Viewports)
17960 if (viewport->PlatformHandle == platform_handle)
17961 return viewport;
17962 return NULL;
17963}
17964
17965void ImGui::SetCurrentViewport(ImGuiWindow *current_window, ImGuiViewportP *viewport)
17966{
17967 ImGuiContext &g = *GImGui;
17968 (void)current_window;
17969
17970 if (viewport)
17971 viewport->LastFrameActive = g.FrameCount;
17972 if (g.CurrentViewport == viewport)
17973 return;
17974 g.CurrentDpiScale = viewport ? viewport->DpiScale : 1.0f;
17975 g.CurrentViewport = viewport;
17976 IM_ASSERT(g.CurrentDpiScale > 0.0f &&
17977 g.CurrentDpiScale < 99.0f); // Typical correct values would be between 1.0f and 4.0f
17978 // IMGUI_DEBUG_LOG_VIEWPORT("[viewport] SetCurrentViewport changed '%s' 0x%08X\n", current_window ?
17979 // current_window->Name : NULL, viewport ? viewport->ID : 0);
17980
17981 // Notify platform layer of viewport changes
17982 // FIXME-DPI: This is only currently used for experimenting with handling of multiple DPI
17983 if (g.CurrentViewport && g.PlatformIO.Platform_OnChangedViewport)
17984 g.PlatformIO.Platform_OnChangedViewport(g.CurrentViewport);
17985}
17986
17987void ImGui::SetWindowViewport(ImGuiWindow *window, ImGuiViewportP *viewport)
17988{
17989 // Abandon viewport
17990 if (window->ViewportOwned && window->Viewport->Window == window)
17991 window->Viewport->Size = ImVec2(0.0f, 0.0f);
17992
17993 window->Viewport = viewport;
17994 window->ViewportId = viewport->ID;
17995 window->ViewportOwned = (viewport->Window == window);
17996}
17997
17998static bool ImGui::GetWindowAlwaysWantOwnViewport(ImGuiWindow *window)
17999{
18000 // Tooltips and menus are not automatically forced into their own viewport when the NoMerge flag is set, however the
18001 // multiplication of viewports makes them more likely to protrude and create their own.
18002 ImGuiContext &g = *GImGui;
18003 if (g.IO.ConfigViewportsNoAutoMerge ||
18004 (window->WindowClass.ViewportFlagsOverrideSet & ImGuiViewportFlags_NoAutoMerge))
18005 if (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable)
18006 if (!window->DockIsActive)
18007 if ((window->Flags &
18008 (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_Tooltip)) == 0)
18009 if ((window->Flags & ImGuiWindowFlags_Popup) == 0 || (window->Flags & ImGuiWindowFlags_Modal) != 0)
18010 return true;
18011 return false;
18012}
18013
18014static bool ImGui::UpdateTryMergeWindowIntoHostViewport(ImGuiWindow *window, ImGuiViewportP *viewport)
18015{
18016 ImGuiContext &g = *GImGui;
18017 if (window->Viewport == viewport)
18018 return false;
18019 if ((viewport->Flags & ImGuiViewportFlags_CanHostOtherWindows) == 0)
18020 return false;
18021 if ((viewport->Flags & ImGuiViewportFlags_IsMinimized) != 0)
18022 return false;
18023 if (!viewport->GetMainRect().Contains(window->Rect()))
18024 return false;
18025 if (GetWindowAlwaysWantOwnViewport(window))
18026 return false;
18027
18028 // FIXME: Can't use g.WindowsFocusOrder[] for root windows only as we care about Z order. If we maintained a
18029 // DisplayOrder along with FocusOrder we could..
18030 for (ImGuiWindow *window_behind : g.Windows)
18031 {
18032 if (window_behind == window)
18033 break;
18034 if (window_behind->WasActive && window_behind->ViewportOwned &&
18035 !(window_behind->Flags & ImGuiWindowFlags_ChildWindow))
18036 if (window_behind->Viewport->GetMainRect().Overlaps(window->Rect()))
18037 return false;
18038 }
18039
18040 // Move to the existing viewport, Move child/hosted windows as well (FIXME-OPT: iterate child)
18041 ImGuiViewportP *old_viewport = window->Viewport;
18042 if (window->ViewportOwned)
18043 for (int n = 0; n < g.Windows.Size; n++)
18044 if (g.Windows[n]->Viewport == old_viewport)
18045 SetWindowViewport(g.Windows[n], viewport);
18046 SetWindowViewport(window, viewport);
18047 BringWindowToDisplayFront(window);
18048
18049 return true;
18050}
18051
18052// FIXME: handle 0 to N host viewports
18053static bool ImGui::UpdateTryMergeWindowIntoHostViewports(ImGuiWindow *window)
18054{
18055 ImGuiContext &g = *GImGui;
18056 return UpdateTryMergeWindowIntoHostViewport(window, g.Viewports[0]);
18057}
18058
18059// Translate Dear ImGui windows when a Host Viewport has been moved
18060// (This additionally keeps windows at the same place when ImGuiConfigFlags_ViewportsEnable is toggled!)
18061void ImGui::TranslateWindowsInViewport(ImGuiViewportP *viewport, const ImVec2 &old_pos, const ImVec2 &new_pos,
18062 const ImVec2 &old_size, const ImVec2 &new_size)
18063{
18064 ImGuiContext &g = *GImGui;
18065 IM_ASSERT(viewport->Window == NULL && (viewport->Flags & ImGuiViewportFlags_CanHostOtherWindows));
18066
18067 // 1) We test if ImGuiConfigFlags_ViewportsEnable was just toggled, which allows us to conveniently
18068 // translate imgui windows from OS-window-local to absolute coordinates or vice-versa.
18069 // 2) If it's not going to fit into the new size, keep it at same absolute position.
18070 // One problem with this is that most Win32 applications doesn't update their render while dragging,
18071 // and so the window will appear to teleport when releasing the mouse.
18072 const bool translate_all_windows = (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable) !=
18073 (g.ConfigFlagsLastFrame & ImGuiConfigFlags_ViewportsEnable);
18074 ImRect test_still_fit_rect(old_pos, old_pos + viewport->Size);
18075 ImVec2 delta_pos = new_pos - old_pos;
18076 for (ImGuiWindow *window : g.Windows) // FIXME-OPT
18077 if (translate_all_windows ||
18078 (window->Viewport == viewport && (old_size == new_size || test_still_fit_rect.Contains(window->Rect()))))
18079 TranslateWindow(window, delta_pos);
18080}
18081
18082// Scale all windows (position, size). Use when e.g. changing DPI. (This is a lossy operation!)
18083void ImGui::ScaleWindowsInViewport(ImGuiViewportP *viewport, float scale)
18084{
18085 ImGuiContext &g = *GImGui;
18086 if (viewport->Window)
18087 {
18088 ScaleWindow(viewport->Window, scale);
18089 }
18090 else
18091 {
18092 for (ImGuiWindow *window : g.Windows)
18093 if (window->Viewport == viewport)
18094 ScaleWindow(window, scale);
18095 }
18096}
18097
18098// If the backend doesn't set MouseLastHoveredViewport or doesn't honor ImGuiViewportFlags_NoInputs, we do a search
18099// ourselves. A) It won't take account of the possibility that non-imgui windows may be in-between our dragged window
18100// and our target window. B) It requires Platform_GetWindowFocus to be implemented by backend.
18101ImGuiViewportP *ImGui::FindHoveredViewportFromPlatformWindowStack(const ImVec2 &mouse_platform_pos)
18102{
18103 ImGuiContext &g = *GImGui;
18104 ImGuiViewportP *best_candidate = NULL;
18105 for (ImGuiViewportP *viewport : g.Viewports)
18106 if (!(viewport->Flags & (ImGuiViewportFlags_NoInputs | ImGuiViewportFlags_IsMinimized)) &&
18107 viewport->GetMainRect().Contains(mouse_platform_pos))
18108 if (best_candidate == NULL || best_candidate->LastFocusedStampCount < viewport->LastFocusedStampCount)
18109 best_candidate = viewport;
18110 return best_candidate;
18111}
18112
18113// Update viewports and monitor infos
18114// Note that this is running even if 'ImGuiConfigFlags_ViewportsEnable' is not set, in order to clear unused viewports
18115// (if any) and update monitor info.
18116static void ImGui::UpdateViewportsNewFrame()
18117{
18118 ImGuiContext &g = *GImGui;
18119 IM_ASSERT(g.PlatformIO.Viewports.Size <= g.Viewports.Size);
18120
18121 // Update Minimized status (we need it first in order to decide if we'll apply Pos/Size of the main viewport)
18122 // Update Focused status
18123 const bool viewports_enabled = (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable) != 0;
18124 if (viewports_enabled)
18125 {
18126 ImGuiViewportP *focused_viewport = NULL;
18127 for (ImGuiViewportP *viewport : g.Viewports)
18128 {
18129 const bool platform_funcs_available = viewport->PlatformWindowCreated;
18130 if (g.PlatformIO.Platform_GetWindowMinimized && platform_funcs_available)
18131 {
18132 bool is_minimized = g.PlatformIO.Platform_GetWindowMinimized(viewport);
18133 if (is_minimized)
18134 viewport->Flags |= ImGuiViewportFlags_IsMinimized;
18135 else
18136 viewport->Flags &= ~ImGuiViewportFlags_IsMinimized;
18137 }
18138
18139 // Update our implicit z-order knowledge of platform windows, which is used when the backend cannot provide
18140 // io.MouseHoveredViewport. When setting Platform_GetWindowFocus, it is expected that the platform backend
18141 // can handle calls without crashing if it doesn't have data stored.
18142 if (g.PlatformIO.Platform_GetWindowFocus && platform_funcs_available)
18143 {
18144 bool is_focused = g.PlatformIO.Platform_GetWindowFocus(viewport);
18145 if (is_focused)
18146 viewport->Flags |= ImGuiViewportFlags_IsFocused;
18147 else
18148 viewport->Flags &= ~ImGuiViewportFlags_IsFocused;
18149 if (is_focused)
18150 focused_viewport = viewport;
18151 }
18152 }
18153
18154 // Focused viewport has changed?
18155 if (focused_viewport && g.PlatformLastFocusedViewportId != focused_viewport->ID)
18156 {
18157 IMGUI_DEBUG_LOG_VIEWPORT(
18158 "[viewport] Focused viewport changed %08X -> %08X, attempting to apply our focus.\n",
18159 g.PlatformLastFocusedViewportId, focused_viewport->ID);
18160 const ImGuiViewport *prev_focused_viewport = FindViewportByID(g.PlatformLastFocusedViewportId);
18161 const bool prev_focused_has_been_destroyed =
18162 (prev_focused_viewport == NULL) || (prev_focused_viewport->PlatformWindowCreated == false);
18163
18164 // Store a tag so we can infer z-order easily from all our windows
18165 // We compare PlatformLastFocusedViewportId so newly created viewports with _NoFocusOnAppearing flag
18166 // will keep the front most stamp instead of losing it back to their parent viewport.
18167 if (focused_viewport->LastFocusedStampCount != g.ViewportFocusedStampCount)
18168 focused_viewport->LastFocusedStampCount = ++g.ViewportFocusedStampCount;
18169 g.PlatformLastFocusedViewportId = focused_viewport->ID;
18170
18171 // Focus associated dear imgui window
18172 // - if focus didn't happen with a click within imgui boundaries, e.g. Clicking platform title bar. (#6299)
18173 // - if focus didn't happen because we destroyed another window (#6462)
18174 // FIXME: perhaps 'FocusTopMostWindowUnderOne()' can handle the 'focused_window->Window != NULL' case as
18175 // well.
18176 const bool apply_imgui_focus_on_focused_viewport = !IsAnyMouseDown() && !prev_focused_has_been_destroyed;
18177 if (apply_imgui_focus_on_focused_viewport)
18178 {
18179 focused_viewport->LastFocusedHadNavWindow |=
18180 (g.NavWindow != NULL) &&
18181 (g.NavWindow->Viewport ==
18182 focused_viewport); // Update so a window changing viewport won't lose focus.
18183 ImGuiFocusRequestFlags focus_request_flags =
18184 ImGuiFocusRequestFlags_UnlessBelowModal | ImGuiFocusRequestFlags_RestoreFocusedChild;
18185 if (focused_viewport->Window != NULL)
18186 FocusWindow(focused_viewport->Window, focus_request_flags);
18187 else if (focused_viewport->LastFocusedHadNavWindow)
18188 FocusTopMostWindowUnderOne(NULL, NULL, focused_viewport,
18189 focus_request_flags); // Focus top most in viewport
18190 else
18191 FocusWindow(NULL, focus_request_flags); // No window had focus last time viewport was focused
18192 }
18193 }
18194 if (focused_viewport)
18195 focused_viewport->LastFocusedHadNavWindow =
18196 (g.NavWindow != NULL) && (g.NavWindow->Viewport == focused_viewport);
18197 }
18198
18199 // Create/update main viewport with current platform position.
18200 // FIXME-VIEWPORT: Size is driven by backend/user code for backward-compatibility but we should aim to make this
18201 // more consistent.
18202 ImGuiViewportP *main_viewport = g.Viewports[0];
18203 IM_ASSERT(main_viewport->ID == IMGUI_VIEWPORT_DEFAULT_ID);
18204 IM_ASSERT(main_viewport->Window == NULL);
18205 ImVec2 main_viewport_pos =
18206 viewports_enabled ? g.PlatformIO.Platform_GetWindowPos(main_viewport) : ImVec2(0.0f, 0.0f);
18207 ImVec2 main_viewport_size = g.IO.DisplaySize;
18208 if (viewports_enabled && (main_viewport->Flags & ImGuiViewportFlags_IsMinimized))
18209 {
18210 main_viewport_pos = main_viewport->Pos; // Preserve last pos/size when minimized (FIXME: We don't do the same
18211 // for Size outside of the viewport path)
18212 main_viewport_size = main_viewport->Size;
18213 }
18214 AddUpdateViewport(NULL, IMGUI_VIEWPORT_DEFAULT_ID, main_viewport_pos, main_viewport_size,
18215 ImGuiViewportFlags_OwnedByApp | ImGuiViewportFlags_CanHostOtherWindows);
18216
18217 g.CurrentDpiScale = 0.0f;
18218 g.CurrentViewport = NULL;
18219 g.MouseViewport = NULL;
18220 for (int n = 0; n < g.Viewports.Size; n++)
18221 {
18222 ImGuiViewportP *viewport = g.Viewports[n];
18223 viewport->Idx = n;
18224
18225 // Erase unused viewports
18226 if (n > 0 && viewport->LastFrameActive < g.FrameCount - 2)
18227 {
18228 DestroyViewport(viewport);
18229 n--;
18230 continue;
18231 }
18232
18233 const bool platform_funcs_available = viewport->PlatformWindowCreated;
18234 if (viewports_enabled)
18235 {
18236 // Update Position and Size (from Platform Window to ImGui) if requested.
18237 // We do it early in the frame instead of waiting for UpdatePlatformWindows() to avoid a frame of lag when
18238 // moving/resizing using OS facilities.
18239 if (!(viewport->Flags & ImGuiViewportFlags_IsMinimized) && platform_funcs_available)
18240 {
18241 // Viewport->WorkPos and WorkSize will be updated below
18242 if (viewport->PlatformRequestMove)
18243 viewport->Pos = viewport->LastPlatformPos = g.PlatformIO.Platform_GetWindowPos(viewport);
18244 if (viewport->PlatformRequestResize)
18245 viewport->Size = viewport->LastPlatformSize = g.PlatformIO.Platform_GetWindowSize(viewport);
18246 }
18247 }
18248
18249 // Update/copy monitor info
18250 UpdateViewportPlatformMonitor(viewport);
18251
18252 // Lock down space taken by menu bars and status bars + query initial insets from backend
18253 // Setup initial value for functions like BeginMainMenuBar(), DockSpaceOverViewport() etc.
18254 viewport->WorkInsetMin = viewport->BuildWorkInsetMin;
18255 viewport->WorkInsetMax = viewport->BuildWorkInsetMax;
18256 viewport->BuildWorkInsetMin = viewport->BuildWorkInsetMax = ImVec2(0.0f, 0.0f);
18257 if (g.PlatformIO.Platform_GetWindowWorkAreaInsets != NULL && platform_funcs_available)
18258 {
18259 ImVec4 insets = g.PlatformIO.Platform_GetWindowWorkAreaInsets(viewport);
18260 IM_ASSERT(insets.x >= 0.0f && insets.y >= 0.0f && insets.z >= 0.0f && insets.w >= 0.0f);
18261 viewport->BuildWorkInsetMin = ImVec2(insets.x, insets.y);
18262 viewport->BuildWorkInsetMax = ImVec2(insets.z, insets.w);
18263 }
18264 viewport->UpdateWorkRect();
18265
18266 // Reset alpha every frame. Users of transparency (docking) needs to request a lower alpha back.
18267 viewport->Alpha = 1.0f;
18268
18269 // Translate Dear ImGui windows when a Host Viewport has been moved
18270 // (This additionally keeps windows at the same place when ImGuiConfigFlags_ViewportsEnable is toggled!)
18271 const ImVec2 viewport_delta_pos = viewport->Pos - viewport->LastPos;
18272 if ((viewport->Flags & ImGuiViewportFlags_CanHostOtherWindows) &&
18273 (viewport_delta_pos.x != 0.0f || viewport_delta_pos.y != 0.0f))
18274 TranslateWindowsInViewport(viewport, viewport->LastPos, viewport->Pos, viewport->LastSize, viewport->Size);
18275
18276 // Update DPI scale
18277 float new_dpi_scale;
18278 if (g.PlatformIO.Platform_GetWindowDpiScale && platform_funcs_available)
18279 new_dpi_scale = g.PlatformIO.Platform_GetWindowDpiScale(viewport);
18280 else if (viewport->PlatformMonitor != -1)
18281 new_dpi_scale = g.PlatformIO.Monitors[viewport->PlatformMonitor].DpiScale;
18282 else
18283 new_dpi_scale = (viewport->DpiScale != 0.0f) ? viewport->DpiScale : 1.0f;
18284 IM_ASSERT(new_dpi_scale > 0.0f &&
18285 new_dpi_scale < 99.0f); // Typical correct values would be between 1.0f and 4.0f
18286 if (viewport->DpiScale != 0.0f && new_dpi_scale != viewport->DpiScale)
18287 {
18288 float scale_factor = new_dpi_scale / viewport->DpiScale;
18289 if (g.IO.ConfigFlags & ImGuiConfigFlags_DpiEnableScaleViewports)
18290 ScaleWindowsInViewport(viewport, scale_factor);
18291 // if (viewport == GetMainViewport())
18292 // g.PlatformInterface.SetWindowSize(viewport, viewport->Size * scale_factor);
18293
18294 // Scale our window moving pivot so that the window will rescale roughly around the mouse position.
18295 // FIXME-VIEWPORT: This currently creates a resizing feedback loop when a window is straddling a DPI
18296 // transition border. (Minor: since our sizes do not perfectly linearly scale, deferring the click offset
18297 // scale until we know the actual window scale ratio may get us slightly more precise mouse positioning.)
18298 // if (g.MovingWindow != NULL && g.MovingWindow->Viewport == viewport)
18299 // g.ActiveIdClickOffset = ImTrunc(g.ActiveIdClickOffset * scale_factor);
18300 }
18301 viewport->DpiScale = new_dpi_scale;
18302 }
18303
18304 // Update fallback monitor
18305 g.PlatformMonitorsFullWorkRect = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX);
18306 if (g.PlatformIO.Monitors.Size == 0)
18307 {
18308 ImGuiPlatformMonitor *monitor = &g.FallbackMonitor;
18309 monitor->MainPos = main_viewport->Pos;
18310 monitor->MainSize = main_viewport->Size;
18311 monitor->WorkPos = main_viewport->WorkPos;
18312 monitor->WorkSize = main_viewport->WorkSize;
18313 monitor->DpiScale = main_viewport->DpiScale;
18314 g.PlatformMonitorsFullWorkRect.Add(monitor->WorkPos);
18315 g.PlatformMonitorsFullWorkRect.Add(monitor->WorkPos + monitor->WorkSize);
18316 }
18317 else
18318 {
18319 g.FallbackMonitor = g.PlatformIO.Monitors[0];
18320 }
18321 for (ImGuiPlatformMonitor &monitor : g.PlatformIO.Monitors)
18322 {
18323 g.PlatformMonitorsFullWorkRect.Add(monitor.WorkPos);
18324 g.PlatformMonitorsFullWorkRect.Add(monitor.WorkPos + monitor.WorkSize);
18325 }
18326
18327 if (!viewports_enabled)
18328 {
18329 g.MouseViewport = main_viewport;
18330 return;
18331 }
18332
18333 // Mouse handling: decide on the actual mouse viewport for this frame between the active/focused viewport and the
18334 // hovered viewport. Note that 'viewport_hovered' should skip over any viewport that has the
18335 // ImGuiViewportFlags_NoInputs flags set.
18336 ImGuiViewportP *viewport_hovered = NULL;
18337 if (g.IO.BackendFlags & ImGuiBackendFlags_HasMouseHoveredViewport)
18338 {
18339 viewport_hovered =
18340 g.IO.MouseHoveredViewport ? (ImGuiViewportP *)FindViewportByID(g.IO.MouseHoveredViewport) : NULL;
18341 if (viewport_hovered && (viewport_hovered->Flags & ImGuiViewportFlags_NoInputs))
18342 viewport_hovered = FindHoveredViewportFromPlatformWindowStack(
18343 g.IO.MousePos); // Backend failed to handle _NoInputs viewport: revert to our fallback.
18344 }
18345 else
18346 {
18347 // If the backend doesn't know how to honor ImGuiViewportFlags_NoInputs, we do a search ourselves. Note that
18348 // this search: A) won't take account of the possibility that non-imgui windows may be in-between our dragged
18349 // window and our target window. B) won't take account of how the backend apply parent<>child relationship to
18350 // secondary viewports, which affects their Z order. C) uses LastFrameAsRefViewport as a flawed replacement for
18351 // the last time a window was focused (we could/should fix that by introducing Focus functions in PlatformIO)
18352 viewport_hovered = FindHoveredViewportFromPlatformWindowStack(g.IO.MousePos);
18353 }
18354 if (viewport_hovered != NULL)
18355 g.MouseLastHoveredViewport = viewport_hovered;
18356 else if (g.MouseLastHoveredViewport == NULL)
18357 g.MouseLastHoveredViewport = g.Viewports[0];
18358
18359 // Update mouse reference viewport
18360 // (when moving a window we aim at its viewport, but this will be overwritten below if we go in drag and drop mode)
18361 // (MovingViewport->Viewport will be NULL in the rare situation where the window disappared while moving, set
18362 // UpdateMouseMovingWindowNewFrame() for details)
18363 if (g.MovingWindow && g.MovingWindow->Viewport)
18364 g.MouseViewport = g.MovingWindow->Viewport;
18365 else
18366 g.MouseViewport = g.MouseLastHoveredViewport;
18367
18368 // When dragging something, always refer to the last hovered viewport.
18369 // - when releasing a moving window we will revert to aiming behind (at viewport_hovered)
18370 // - when we are between viewports, our dragged preview will tend to show in the last viewport _even_ if we don't
18371 // have tooltips in their viewports (when lacking monitor info)
18372 // - consider the case of holding on a menu item to browse child menus: even thou a mouse button is held, there's no
18373 // active id because menu items only react on mouse release.
18374 // FIXME-VIEWPORT: This is essentially broken, when ImGuiBackendFlags_HasMouseHoveredViewport is set we want to
18375 // trust when viewport_hovered==NULL and use that.
18376 const bool is_mouse_dragging_with_an_expected_destination = g.DragDropActive;
18377 if (is_mouse_dragging_with_an_expected_destination && viewport_hovered == NULL)
18378 viewport_hovered = g.MouseLastHoveredViewport;
18379 if (is_mouse_dragging_with_an_expected_destination || g.ActiveId == 0 || !IsAnyMouseDown())
18380 if (viewport_hovered != NULL && viewport_hovered != g.MouseViewport &&
18381 !(viewport_hovered->Flags & ImGuiViewportFlags_NoInputs))
18382 g.MouseViewport = viewport_hovered;
18383
18384 IM_ASSERT(g.MouseViewport != NULL);
18385}
18386
18387// Update user-facing viewport list (g.Viewports -> g.PlatformIO.Viewports after filtering out some)
18388static void ImGui::UpdateViewportsEndFrame()
18389{
18390 ImGuiContext &g = *GImGui;
18391 g.PlatformIO.Viewports.resize(0);
18392 for (int i = 0; i < g.Viewports.Size; i++)
18393 {
18394 ImGuiViewportP *viewport = g.Viewports[i];
18395 viewport->LastPos = viewport->Pos;
18396 viewport->LastSize = viewport->Size;
18397 if (viewport->LastFrameActive < g.FrameCount || viewport->Size.x <= 0.0f || viewport->Size.y <= 0.0f)
18398 if (i > 0) // Always include main viewport in the list
18399 continue;
18400 if (viewport->Window && !IsWindowActiveAndVisible(viewport->Window))
18401 continue;
18402 if (i > 0)
18403 IM_ASSERT(viewport->Window != NULL);
18404 g.PlatformIO.Viewports.push_back(viewport);
18405 }
18406 g.Viewports[0]->ClearRequestFlags(); // Clear main viewport flags because UpdatePlatformWindows() won't do it and
18407 // may not even be called
18408}
18409
18410// FIXME: We should ideally refactor the system to call this every frame (we currently don't)
18411ImGuiViewportP *ImGui::AddUpdateViewport(ImGuiWindow *window, ImGuiID id, const ImVec2 &pos, const ImVec2 &size,
18412 ImGuiViewportFlags flags)
18413{
18414 ImGuiContext &g = *GImGui;
18415 IM_ASSERT(id != 0);
18416
18417 flags |= ImGuiViewportFlags_IsPlatformWindow;
18418 if (window != NULL)
18419 {
18420 if (g.MovingWindow && g.MovingWindow->RootWindowDockTree == window)
18421 flags |= ImGuiViewportFlags_NoInputs | ImGuiViewportFlags_NoFocusOnAppearing;
18422 if ((window->Flags & ImGuiWindowFlags_NoMouseInputs) && (window->Flags & ImGuiWindowFlags_NoNavInputs))
18423 flags |= ImGuiViewportFlags_NoInputs;
18424 if (window->Flags & ImGuiWindowFlags_NoFocusOnAppearing)
18425 flags |= ImGuiViewportFlags_NoFocusOnAppearing;
18426 }
18427
18428 ImGuiViewportP *viewport = (ImGuiViewportP *)FindViewportByID(id);
18429 if (viewport)
18430 {
18431 // Always update for main viewport as we are already pulling correct platform pos/size (see #4900)
18432 ImVec2 prev_pos = viewport->Pos;
18433 ImVec2 prev_size = viewport->Size;
18434 if (!viewport->PlatformRequestMove || viewport->ID == IMGUI_VIEWPORT_DEFAULT_ID)
18435 viewport->Pos = pos;
18436 if (!viewport->PlatformRequestResize || viewport->ID == IMGUI_VIEWPORT_DEFAULT_ID)
18437 viewport->Size = size;
18438 viewport->Flags = flags | (viewport->Flags & (ImGuiViewportFlags_IsMinimized |
18439 ImGuiViewportFlags_IsFocused)); // Preserve existing flags
18440 if (prev_pos != viewport->Pos || prev_size != viewport->Size)
18441 UpdateViewportPlatformMonitor(viewport);
18442 }
18443 else
18444 {
18445 // New viewport
18446 viewport = IM_NEW(ImGuiViewportP)();
18447 viewport->ID = id;
18448 viewport->Idx = g.Viewports.Size;
18449 viewport->Pos = viewport->LastPos = pos;
18450 viewport->Size = viewport->LastSize = size;
18451 viewport->Flags = flags;
18452 UpdateViewportPlatformMonitor(viewport);
18453 g.Viewports.push_back(viewport);
18454 g.ViewportCreatedCount++;
18455 IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Add Viewport %08X '%s'\n", id, window ? window->Name : "<NULL>");
18456
18457 // We normally setup for all viewports in NewFrame() but here need to handle the mid-frame creation of a new
18458 // viewport. We need to extend the fullscreen clip rect so the OverlayDrawList clip is correct for that the
18459 // first frame
18460 g.DrawListSharedData.ClipRectFullscreen.x = ImMin(g.DrawListSharedData.ClipRectFullscreen.x, viewport->Pos.x);
18461 g.DrawListSharedData.ClipRectFullscreen.y = ImMin(g.DrawListSharedData.ClipRectFullscreen.y, viewport->Pos.y);
18462 g.DrawListSharedData.ClipRectFullscreen.z =
18463 ImMax(g.DrawListSharedData.ClipRectFullscreen.z, viewport->Pos.x + viewport->Size.x);
18464 g.DrawListSharedData.ClipRectFullscreen.w =
18465 ImMax(g.DrawListSharedData.ClipRectFullscreen.w, viewport->Pos.y + viewport->Size.y);
18466
18467 // Store initial DpiScale before the OS platform window creation, based on expected monitor data.
18468 // This is so we can select an appropriate font size on the first frame of our window lifetime
18469 if (viewport->PlatformMonitor != -1)
18470 viewport->DpiScale = g.PlatformIO.Monitors[viewport->PlatformMonitor].DpiScale;
18471 else
18472 viewport->DpiScale = 1.0f;
18473 }
18474
18475 viewport->Window = window;
18476 viewport->LastFrameActive = g.FrameCount;
18477 viewport->UpdateWorkRect();
18478 IM_ASSERT(window == NULL || viewport->ID == window->ID);
18479
18480 if (window != NULL)
18481 window->ViewportOwned = true;
18482
18483 return viewport;
18484}
18485
18486static void ImGui::DestroyViewport(ImGuiViewportP *viewport)
18487{
18488 // Clear references to this viewport in windows (window->ViewportId becomes the master data)
18489 ImGuiContext &g = *GImGui;
18490 for (ImGuiWindow *window : g.Windows)
18491 {
18492 if (window->Viewport != viewport)
18493 continue;
18494 window->Viewport = NULL;
18495 window->ViewportOwned = false;
18496 }
18497 if (viewport == g.MouseLastHoveredViewport)
18498 g.MouseLastHoveredViewport = NULL;
18499
18500 // Destroy
18501 IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Delete Viewport %08X '%s'\n", viewport->ID,
18502 viewport->Window ? viewport->Window->Name : "n/a");
18503 DestroyPlatformWindow(viewport); // In most circumstances the platform window will already be destroyed here.
18504 IM_ASSERT(g.PlatformIO.Viewports.contains(viewport) == false);
18505 IM_ASSERT(g.Viewports[viewport->Idx] == viewport);
18506 g.Viewports.erase(g.Viewports.Data + viewport->Idx);
18507 IM_DELETE(viewport);
18508}
18509
18510// FIXME-VIEWPORT: This is all super messy and ought to be clarified or rewritten.
18511static void ImGui::WindowSelectViewport(ImGuiWindow *window)
18512{
18513 ImGuiContext &g = *GImGui;
18514 ImGuiWindowFlags flags = window->Flags;
18515 window->ViewportAllowPlatformMonitorExtend = -1;
18516
18517 // Restore main viewport if multi-viewport is not supported by the backend
18518 ImGuiViewportP *main_viewport = (ImGuiViewportP *)(void *)GetMainViewport();
18519 if (!(g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable))
18520 {
18521 SetWindowViewport(window, main_viewport);
18522 return;
18523 }
18524 window->ViewportOwned = false;
18525
18526 // Appearing popups reset their viewport so they can inherit again
18527 if ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) && window->Appearing)
18528 {
18529 window->Viewport = NULL;
18530 window->ViewportId = 0;
18531 }
18532
18533 if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasViewport) == 0)
18534 {
18535 // By default inherit from parent window
18536 if (window->Viewport == NULL && window->ParentWindow &&
18537 (!window->ParentWindow->IsFallbackWindow || window->ParentWindow->WasActive))
18538 window->Viewport = window->ParentWindow->Viewport;
18539
18540 // Attempt to restore saved viewport id (= window that hasn't been activated yet), try to restore the viewport
18541 // based on saved 'window->ViewportPos' restored from .ini file
18542 if (window->Viewport == NULL && window->ViewportId != 0)
18543 {
18544 window->Viewport = (ImGuiViewportP *)FindViewportByID(window->ViewportId);
18545 if (window->Viewport == NULL && window->ViewportPos.x != FLT_MAX && window->ViewportPos.y != FLT_MAX)
18546 window->Viewport =
18547 AddUpdateViewport(window, window->ID, window->ViewportPos, window->Size, ImGuiViewportFlags_None);
18548 }
18549 }
18550
18551 bool lock_viewport = false;
18552 if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasViewport)
18553 {
18554 // Code explicitly request a viewport
18555 window->Viewport = (ImGuiViewportP *)FindViewportByID(g.NextWindowData.ViewportId);
18556 window->ViewportId = g.NextWindowData.ViewportId; // Store ID even if Viewport isn't resolved yet.
18557 if (window->Viewport && (window->Flags & ImGuiWindowFlags_DockNodeHost) != 0 &&
18558 window->Viewport->Window != NULL)
18559 {
18560 window->Viewport->Window = window;
18561 window->Viewport->ID = window->ViewportId = window->ID; // Overwrite ID (always owned by node)
18562 }
18563 lock_viewport = true;
18564 }
18565 else if ((flags & ImGuiWindowFlags_ChildWindow) || (flags & ImGuiWindowFlags_ChildMenu))
18566 {
18567 // Always inherit viewport from parent window
18568 if (window->DockNode && window->DockNode->HostWindow)
18569 IM_ASSERT(window->DockNode->HostWindow->Viewport == window->ParentWindow->Viewport);
18570 window->Viewport = window->ParentWindow->Viewport;
18571 }
18572 else if (window->DockNode && window->DockNode->HostWindow)
18573 {
18574 // This covers the "always inherit viewport from parent window" case for when a window reattach to a node that
18575 // was just created mid-frame
18576 window->Viewport = window->DockNode->HostWindow->Viewport;
18577 }
18578 else if (flags & ImGuiWindowFlags_Tooltip)
18579 {
18580 window->Viewport = g.MouseViewport;
18581 }
18582 else if (GetWindowAlwaysWantOwnViewport(window))
18583 {
18584 window->Viewport = AddUpdateViewport(window, window->ID, window->Pos, window->Size, ImGuiViewportFlags_None);
18585 }
18586 else if (g.MovingWindow && g.MovingWindow->RootWindowDockTree == window && IsMousePosValid())
18587 {
18588 if (window->Viewport != NULL && window->Viewport->Window == window)
18589 window->Viewport =
18590 AddUpdateViewport(window, window->ID, window->Pos, window->Size, ImGuiViewportFlags_None);
18591 }
18592 else
18593 {
18594 // Merge into host viewport?
18595 // We cannot test window->ViewportOwned as it set lower in the function.
18596 // Testing (g.ActiveId == 0 || g.ActiveIdAllowOverlap) to avoid merging during a short-term widget interaction.
18597 // Main intent was to avoid during resize (see #4212)
18598 bool try_to_merge_into_host_viewport =
18599 (window->Viewport && window == window->Viewport->Window && (g.ActiveId == 0 || g.ActiveIdAllowOverlap));
18600 if (try_to_merge_into_host_viewport)
18601 UpdateTryMergeWindowIntoHostViewports(window);
18602 }
18603
18604 // Fallback: merge in default viewport if z-order matches, otherwise create a new viewport
18605 if (window->Viewport == NULL)
18606 if (!UpdateTryMergeWindowIntoHostViewport(window, main_viewport))
18607 window->Viewport =
18608 AddUpdateViewport(window, window->ID, window->Pos, window->Size, ImGuiViewportFlags_None);
18609
18610 // Mark window as allowed to protrude outside of its viewport and into the current monitor
18611 if (!lock_viewport)
18612 {
18613 if (flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup))
18614 {
18615 // We need to take account of the possibility that mouse may become invalid.
18616 // Popups/Tooltip always set ViewportAllowPlatformMonitorExtend so GetWindowAllowedExtentRect() will return
18617 // full monitor bounds.
18618 ImVec2 mouse_ref =
18619 (flags & ImGuiWindowFlags_Tooltip) ? g.IO.MousePos : g.BeginPopupStack.back().OpenMousePos;
18620 bool use_mouse_ref = (!g.NavCursorVisible || !g.NavHighlightItemUnderNav || !g.NavWindow);
18621 bool mouse_valid = IsMousePosValid(&mouse_ref);
18622 if ((window->Appearing || (flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_ChildMenu))) &&
18623 (!use_mouse_ref || mouse_valid))
18624 window->ViewportAllowPlatformMonitorExtend =
18625 FindPlatformMonitorForPos((use_mouse_ref && mouse_valid) ? mouse_ref : NavCalcPreferredRefPos());
18626 else
18627 window->ViewportAllowPlatformMonitorExtend = window->Viewport->PlatformMonitor;
18628 }
18629 else if (window->Viewport && window != window->Viewport->Window && window->Viewport->Window &&
18630 !(flags & ImGuiWindowFlags_ChildWindow) && window->DockNode == NULL)
18631 {
18632 // When called from Begin() we don't have access to a proper version of the Hidden flag yet, so we replicate
18633 // this code.
18634 const bool will_be_visible = (window->DockIsActive && !window->DockTabIsVisible) ? false : true;
18635 if ((window->Flags & ImGuiWindowFlags_DockNodeHost) && window->Viewport->LastFrameActive < g.FrameCount &&
18636 will_be_visible)
18637 {
18638 // Steal/transfer ownership
18639 IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Window '%s' steal Viewport %08X from Window '%s'\n", window->Name,
18640 window->Viewport->ID, window->Viewport->Window->Name);
18641 window->Viewport->Window = window;
18642 window->Viewport->ID = window->ID;
18643 window->Viewport->LastNameHash = 0;
18644 }
18645 else if (!UpdateTryMergeWindowIntoHostViewports(window)) // Merge?
18646 {
18647 // New viewport
18648 window->Viewport = AddUpdateViewport(window, window->ID, window->Pos, window->Size,
18649 ImGuiViewportFlags_NoFocusOnAppearing);
18650 }
18651 }
18652 else if (window->ViewportAllowPlatformMonitorExtend < 0 && (flags & ImGuiWindowFlags_ChildWindow) == 0)
18653 {
18654 // Regular (non-child, non-popup) windows by default are also allowed to protrude
18655 // Child windows are kept contained within their parent.
18656 window->ViewportAllowPlatformMonitorExtend = window->Viewport->PlatformMonitor;
18657 }
18658 }
18659
18660 // Update flags
18661 window->ViewportOwned = (window == window->Viewport->Window);
18662 window->ViewportId = window->Viewport->ID;
18663
18664 // If the OS window has a title bar, hide our imgui title bar
18665 // if (window->ViewportOwned && !(window->Viewport->Flags & ImGuiViewportFlags_NoDecoration))
18666 // window->Flags |= ImGuiWindowFlags_NoTitleBar;
18667}
18668
18669void ImGui::WindowSyncOwnedViewport(ImGuiWindow *window, ImGuiWindow *parent_window_in_stack)
18670{
18671 ImGuiContext &g = *GImGui;
18672
18673 bool viewport_rect_changed = false;
18674
18675 // Synchronize window --> viewport in most situations
18676 // Synchronize viewport -> window in case the platform window has been moved or resized from the OS/WM
18677 if (window->Viewport->PlatformRequestMove)
18678 {
18679 window->Pos = window->Viewport->Pos;
18680 MarkIniSettingsDirty(window);
18681 }
18682 else if (memcmp(&window->Viewport->Pos, &window->Pos, sizeof(window->Pos)) != 0)
18683 {
18684 viewport_rect_changed = true;
18685 window->Viewport->Pos = window->Pos;
18686 }
18687
18688 if (window->Viewport->PlatformRequestResize)
18689 {
18690 window->Size = window->SizeFull = window->Viewport->Size;
18691 MarkIniSettingsDirty(window);
18692 }
18693 else if (memcmp(&window->Viewport->Size, &window->Size, sizeof(window->Size)) != 0)
18694 {
18695 viewport_rect_changed = true;
18696 window->Viewport->Size = window->Size;
18697 }
18698 window->Viewport->UpdateWorkRect();
18699
18700 // The viewport may have changed monitor since the global update in UpdateViewportsNewFrame()
18701 // Either a SetNextWindowPos() call in the current frame or a SetWindowPos() call in the previous frame may have
18702 // this effect.
18703 if (viewport_rect_changed)
18704 UpdateViewportPlatformMonitor(window->Viewport);
18705
18706 // Update common viewport flags
18707 const ImGuiViewportFlags viewport_flags_to_clear = ImGuiViewportFlags_TopMost | ImGuiViewportFlags_NoTaskBarIcon |
18708 ImGuiViewportFlags_NoDecoration |
18709 ImGuiViewportFlags_NoRendererClear;
18710 ImGuiViewportFlags viewport_flags = window->Viewport->Flags & ~viewport_flags_to_clear;
18711 ImGuiWindowFlags window_flags = window->Flags;
18712 const bool is_modal = (window_flags & ImGuiWindowFlags_Modal) != 0;
18713 const bool is_short_lived_floating_window =
18714 (window_flags & (ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup)) != 0;
18715 if (window_flags & ImGuiWindowFlags_Tooltip)
18716 viewport_flags |= ImGuiViewportFlags_TopMost;
18717 if ((g.IO.ConfigViewportsNoTaskBarIcon || is_short_lived_floating_window) && !is_modal)
18718 viewport_flags |= ImGuiViewportFlags_NoTaskBarIcon;
18719 if (g.IO.ConfigViewportsNoDecoration || is_short_lived_floating_window)
18720 viewport_flags |= ImGuiViewportFlags_NoDecoration;
18721
18722 // Not correct to set modal as topmost because:
18723 // - Because other popups can be stacked above a modal (e.g. combo box in a modal)
18724 // - ImGuiViewportFlags_TopMost is currently handled different in backends: in Win32 it is "appear top most" whereas
18725 // in GLFW and SDL it is "stay topmost"
18726 // if (flags & ImGuiWindowFlags_Modal)
18727 // viewport_flags |= ImGuiViewportFlags_TopMost;
18728
18729 // For popups and menus that may be protruding out of their parent viewport, we enable _NoFocusOnClick so that
18730 // clicking on them won't steal the OS focus away from their parent window (which may be reflected in OS the title
18731 // bar decoration). Setting _NoFocusOnClick would technically prevent us from bringing back to front in case they
18732 // are being covered by an OS window from a different app, but it shouldn't be much of a problem considering those
18733 // are already popups that are closed when clicking elsewhere.
18734 if (is_short_lived_floating_window && !is_modal)
18735 viewport_flags |= ImGuiViewportFlags_NoFocusOnAppearing | ImGuiViewportFlags_NoFocusOnClick;
18736
18737 // We can overwrite viewport flags using ImGuiWindowClass (advanced users)
18738 if (window->WindowClass.ViewportFlagsOverrideSet)
18739 viewport_flags |= window->WindowClass.ViewportFlagsOverrideSet;
18740 if (window->WindowClass.ViewportFlagsOverrideClear)
18741 viewport_flags &= ~window->WindowClass.ViewportFlagsOverrideClear;
18742
18743 // We can also tell the backend that clearing the platform window won't be necessary,
18744 // as our window background is filling the viewport and we have disabled BgAlpha.
18745 // FIXME: Work on support for per-viewport transparency (#2766)
18746 if (!(window_flags & ImGuiWindowFlags_NoBackground))
18747 viewport_flags |= ImGuiViewportFlags_NoRendererClear;
18748
18749 window->Viewport->Flags = viewport_flags;
18750
18751 // Update parent viewport ID
18752 // (the !IsFallbackWindow test mimic the one done in WindowSelectViewport())
18753 if (window->WindowClass.ParentViewportId != (ImGuiID)-1)
18754 window->Viewport->ParentViewportId = window->WindowClass.ParentViewportId;
18755 else if ((window_flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) && parent_window_in_stack &&
18756 (!parent_window_in_stack->IsFallbackWindow || parent_window_in_stack->WasActive))
18757 window->Viewport->ParentViewportId = parent_window_in_stack->Viewport->ID;
18758 else
18759 window->Viewport->ParentViewportId = g.IO.ConfigViewportsNoDefaultParent ? 0 : IMGUI_VIEWPORT_DEFAULT_ID;
18760}
18761
18762// Called by user at the end of the main loop, after EndFrame()
18763// This will handle the creation/update of all OS windows via function defined in the ImGuiPlatformIO api.
18764void ImGui::UpdatePlatformWindows()
18765{
18766 ImGuiContext &g = *GImGui;
18767 IM_ASSERT(g.FrameCountEnded == g.FrameCount &&
18768 "Forgot to call Render() or EndFrame() before UpdatePlatformWindows()?");
18769 IM_ASSERT(g.FrameCountPlatformEnded < g.FrameCount);
18770 g.FrameCountPlatformEnded = g.FrameCount;
18771 if (!(g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable))
18772 return;
18773
18774 // Create/resize/destroy platform windows to match each active viewport.
18775 // Skip the main viewport (index 0), which is always fully handled by the application!
18776 for (int i = 1; i < g.Viewports.Size; i++)
18777 {
18778 ImGuiViewportP *viewport = g.Viewports[i];
18779
18780 // Destroy platform window if the viewport hasn't been submitted or if it is hosting a hidden window
18781 // (the implicit/fallback Debug##Default window will be registering its viewport then be disabled, causing a
18782 // dummy DestroyPlatformWindow to be made each frame)
18783 bool destroy_platform_window = false;
18784 destroy_platform_window |= (viewport->LastFrameActive < g.FrameCount - 1);
18785 destroy_platform_window |= (viewport->Window && !IsWindowActiveAndVisible(viewport->Window));
18786 if (destroy_platform_window)
18787 {
18788 DestroyPlatformWindow(viewport);
18789 continue;
18790 }
18791
18792 // New windows that appears directly in a new viewport won't always have a size on their first frame
18793 if (viewport->LastFrameActive < g.FrameCount || viewport->Size.x <= 0 || viewport->Size.y <= 0)
18794 continue;
18795
18796 // Create window
18797 const bool is_new_platform_window = (viewport->PlatformWindowCreated == false);
18798 if (is_new_platform_window)
18799 {
18800 IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Create Platform Window %08X '%s'\n", viewport->ID,
18801 viewport->Window ? viewport->Window->Name : "n/a");
18802 g.PlatformIO.Platform_CreateWindow(viewport);
18803 if (g.PlatformIO.Renderer_CreateWindow != NULL)
18804 g.PlatformIO.Renderer_CreateWindow(viewport);
18805 g.PlatformWindowsCreatedCount++;
18806 viewport->LastNameHash = 0;
18807 viewport->LastPlatformPos = viewport->LastPlatformSize =
18808 ImVec2(FLT_MAX, FLT_MAX); // By clearing those we'll enforce a call to Platform_SetWindowPos/Size below,
18809 // before Platform_ShowWindow (FIXME: Is that necessary?)
18810 viewport->LastRendererSize = viewport->Size; // We don't need to call Renderer_SetWindowSize() as it is
18811 // expected Renderer_CreateWindow() already did it.
18812 viewport->PlatformWindowCreated = true;
18813 }
18814
18815 // Apply Position and Size (from ImGui to Platform/Renderer backends)
18816 if ((viewport->LastPlatformPos.x != viewport->Pos.x || viewport->LastPlatformPos.y != viewport->Pos.y) &&
18817 !viewport->PlatformRequestMove)
18818 g.PlatformIO.Platform_SetWindowPos(viewport, viewport->Pos);
18819 if ((viewport->LastPlatformSize.x != viewport->Size.x || viewport->LastPlatformSize.y != viewport->Size.y) &&
18820 !viewport->PlatformRequestResize)
18821 g.PlatformIO.Platform_SetWindowSize(viewport, viewport->Size);
18822 if ((viewport->LastRendererSize.x != viewport->Size.x || viewport->LastRendererSize.y != viewport->Size.y) &&
18823 g.PlatformIO.Renderer_SetWindowSize)
18824 g.PlatformIO.Renderer_SetWindowSize(viewport, viewport->Size);
18825 viewport->LastPlatformPos = viewport->Pos;
18826 viewport->LastPlatformSize = viewport->LastRendererSize = viewport->Size;
18827
18828 // Update title bar (if it changed)
18829 if (ImGuiWindow *window_for_title = GetWindowForTitleDisplay(viewport->Window))
18830 {
18831 const char *title_begin = window_for_title->Name;
18832 char *title_end = (char *)(intptr_t)FindRenderedTextEnd(title_begin);
18833 const ImGuiID title_hash = ImHashStr(title_begin, title_end - title_begin);
18834 if (viewport->LastNameHash != title_hash)
18835 {
18836 char title_end_backup_c = *title_end;
18837 *title_end = 0; // Cut existing buffer short instead of doing an alloc/free, no small gain.
18838 g.PlatformIO.Platform_SetWindowTitle(viewport, title_begin);
18839 *title_end = title_end_backup_c;
18840 viewport->LastNameHash = title_hash;
18841 }
18842 }
18843
18844 // Update alpha (if it changed)
18845 if (viewport->LastAlpha != viewport->Alpha && g.PlatformIO.Platform_SetWindowAlpha)
18846 g.PlatformIO.Platform_SetWindowAlpha(viewport, viewport->Alpha);
18847 viewport->LastAlpha = viewport->Alpha;
18848
18849 // Optional, general purpose call to allow the backend to perform general book-keeping even if things haven't
18850 // changed.
18851 if (g.PlatformIO.Platform_UpdateWindow)
18852 g.PlatformIO.Platform_UpdateWindow(viewport);
18853
18854 if (is_new_platform_window)
18855 {
18856 // On startup ensure new platform window don't steal focus (give it a few frames, as nested contents may
18857 // lead to viewport being created a few frames late)
18858 if (g.FrameCount < 3)
18859 viewport->Flags |= ImGuiViewportFlags_NoFocusOnAppearing;
18860
18861 // Show window
18862 g.PlatformIO.Platform_ShowWindow(viewport);
18863
18864 // Even without focus, we assume the window becomes front-most.
18865 // This is useful for our platform z-order heuristic when io.MouseHoveredViewport is not available.
18866 if (viewport->LastFocusedStampCount != g.ViewportFocusedStampCount)
18867 viewport->LastFocusedStampCount = ++g.ViewportFocusedStampCount;
18868 }
18869
18870 // Clear request flags
18871 viewport->ClearRequestFlags();
18872 }
18873}
18874
18875// This is a default/basic function for performing the rendering/swap of multiple Platform Windows.
18876// Custom renderers may prefer to not call this function at all, and instead iterate the publicly exposed platform data
18877// and handle rendering/sync themselves. The Render/Swap functions stored in ImGuiPlatformIO are merely here to allow
18878// for this helper to exist, but you can do it yourself:
18879//
18880// ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
18881// for (int i = 1; i < platform_io.Viewports.Size; i++)
18882// if ((platform_io.Viewports[i]->Flags & ImGuiViewportFlags_Minimized) == 0)
18883// MyRenderFunction(platform_io.Viewports[i], my_args);
18884// for (int i = 1; i < platform_io.Viewports.Size; i++)
18885// if ((platform_io.Viewports[i]->Flags & ImGuiViewportFlags_Minimized) == 0)
18886// MySwapBufferFunction(platform_io.Viewports[i], my_args);
18887//
18888void ImGui::RenderPlatformWindowsDefault(void *platform_render_arg, void *renderer_render_arg)
18889{
18890 // Skip the main viewport (index 0), which is always fully handled by the application!
18891 ImGuiPlatformIO &platform_io = ImGui::GetPlatformIO();
18892 for (int i = 1; i < platform_io.Viewports.Size; i++)
18893 {
18894 ImGuiViewport *viewport = platform_io.Viewports[i];
18895 if (viewport->Flags & ImGuiViewportFlags_IsMinimized)
18896 continue;
18897 if (platform_io.Platform_RenderWindow)
18898 platform_io.Platform_RenderWindow(viewport, platform_render_arg);
18899 if (platform_io.Renderer_RenderWindow)
18900 platform_io.Renderer_RenderWindow(viewport, renderer_render_arg);
18901 }
18902 for (int i = 1; i < platform_io.Viewports.Size; i++)
18903 {
18904 ImGuiViewport *viewport = platform_io.Viewports[i];
18905 if (viewport->Flags & ImGuiViewportFlags_IsMinimized)
18906 continue;
18907 if (platform_io.Platform_SwapBuffers)
18908 platform_io.Platform_SwapBuffers(viewport, platform_render_arg);
18909 if (platform_io.Renderer_SwapBuffers)
18910 platform_io.Renderer_SwapBuffers(viewport, renderer_render_arg);
18911 }
18912}
18913
18914static int ImGui::FindPlatformMonitorForPos(const ImVec2 &pos)
18915{
18916 ImGuiContext &g = *GImGui;
18917 for (int monitor_n = 0; monitor_n < g.PlatformIO.Monitors.Size; monitor_n++)
18918 {
18919 const ImGuiPlatformMonitor &monitor = g.PlatformIO.Monitors[monitor_n];
18920 if (ImRect(monitor.MainPos, monitor.MainPos + monitor.MainSize).Contains(pos))
18921 return monitor_n;
18922 }
18923 return -1;
18924}
18925
18926// Search for the monitor with the largest intersection area with the given rectangle
18927// We generally try to avoid searching loops but the monitor count should be very small here
18928// FIXME-OPT: We could test the last monitor used for that viewport first, and early
18929static int ImGui::FindPlatformMonitorForRect(const ImRect &rect)
18930{
18931 ImGuiContext &g = *GImGui;
18932
18933 const int monitor_count = g.PlatformIO.Monitors.Size;
18934 if (monitor_count <= 1)
18935 return monitor_count - 1;
18936
18937 // Use a minimum threshold of 1.0f so a zero-sized rect won't false positive, and will still find the correct
18938 // monitor given its position. This is necessary for tooltips which always resize down to zero at first.
18939 const float surface_threshold = ImMax(rect.GetWidth() * rect.GetHeight() * 0.5f, 1.0f);
18940 int best_monitor_n = 0; // Default to the first monitor as fallback
18941 float best_monitor_surface = 0.001f;
18942
18943 for (int monitor_n = 0; monitor_n < g.PlatformIO.Monitors.Size && best_monitor_surface < surface_threshold;
18944 monitor_n++)
18945 {
18946 const ImGuiPlatformMonitor &monitor = g.PlatformIO.Monitors[monitor_n];
18947 const ImRect monitor_rect = ImRect(monitor.MainPos, monitor.MainPos + monitor.MainSize);
18948 if (monitor_rect.Contains(rect))
18949 return monitor_n;
18950 ImRect overlapping_rect = rect;
18951 overlapping_rect.ClipWithFull(monitor_rect);
18952 float overlapping_surface = overlapping_rect.GetWidth() * overlapping_rect.GetHeight();
18953 if (overlapping_surface < best_monitor_surface)
18954 continue;
18955 best_monitor_surface = overlapping_surface;
18956 best_monitor_n = monitor_n;
18957 }
18958 return best_monitor_n;
18959}
18960
18961// Update monitor from viewport rectangle (we'll use this info to clamp windows and save windows lost in a removed
18962// monitor)
18963static void ImGui::UpdateViewportPlatformMonitor(ImGuiViewportP *viewport)
18964{
18965 viewport->PlatformMonitor = (short)FindPlatformMonitorForRect(viewport->GetMainRect());
18966}
18967
18968// Return value is always != NULL, but don't hold on it across frames.
18969const ImGuiPlatformMonitor *ImGui::GetViewportPlatformMonitor(ImGuiViewport *viewport_p)
18970{
18971 ImGuiContext &g = *GImGui;
18972 ImGuiViewportP *viewport = (ImGuiViewportP *)(void *)viewport_p;
18973 int monitor_idx = viewport->PlatformMonitor;
18974 if (monitor_idx >= 0 && monitor_idx < g.PlatformIO.Monitors.Size)
18975 return &g.PlatformIO.Monitors[monitor_idx];
18976 return &g.FallbackMonitor;
18977}
18978
18979void ImGui::DestroyPlatformWindow(ImGuiViewportP *viewport)
18980{
18981 ImGuiContext &g = *GImGui;
18982 if (viewport->PlatformWindowCreated)
18983 {
18984 IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Destroy Platform Window %08X '%s'\n", viewport->ID,
18985 viewport->Window ? viewport->Window->Name : "n/a");
18986 if (g.PlatformIO.Renderer_DestroyWindow)
18987 g.PlatformIO.Renderer_DestroyWindow(viewport);
18988 if (g.PlatformIO.Platform_DestroyWindow)
18989 g.PlatformIO.Platform_DestroyWindow(viewport);
18990 IM_ASSERT(viewport->RendererUserData == NULL && viewport->PlatformUserData == NULL);
18991
18992 // Don't clear PlatformWindowCreated for the main viewport, as we initially set that up to true in Initialize()
18993 // The righter way may be to leave it to the backend to set this flag all-together, and made the flag public.
18994 if (viewport->ID != IMGUI_VIEWPORT_DEFAULT_ID)
18995 viewport->PlatformWindowCreated = false;
18996 }
18997 else
18998 {
18999 IM_ASSERT(viewport->RendererUserData == NULL && viewport->PlatformUserData == NULL &&
19000 viewport->PlatformHandle == NULL);
19001 }
19002 viewport->RendererUserData = viewport->PlatformUserData = viewport->PlatformHandle = NULL;
19003 viewport->ClearRequestFlags();
19004}
19005
19006void ImGui::DestroyPlatformWindows()
19007{
19008 // We call the destroy window on every viewport (including the main viewport, index 0) to give a chance to the
19009 // backend to clear any data they may have stored in e.g. PlatformUserData, RendererUserData. It is convenient for
19010 // the platform backend code to store something in the main viewport, in order for e.g. the mouse handling code to
19011 // operator a consistent manner. It is expected that the backend can handle calls to
19012 // Renderer_DestroyWindow/Platform_DestroyWindow without crashing if it doesn't have data stored.
19013 ImGuiContext &g = *GImGui;
19014 for (ImGuiViewportP *viewport : g.Viewports)
19015 DestroyPlatformWindow(viewport);
19016}
19017
19018//-----------------------------------------------------------------------------
19019// [SECTION] DOCKING
19020//-----------------------------------------------------------------------------
19021// Docking: Internal Types
19022// Docking: Forward Declarations
19023// Docking: ImGuiDockContext
19024// Docking: ImGuiDockContext Docking/Undocking functions
19025// Docking: ImGuiDockNode
19026// Docking: ImGuiDockNode Tree manipulation functions
19027// Docking: Public Functions (SetWindowDock, DockSpace, DockSpaceOverViewport)
19028// Docking: Builder Functions
19029// Docking: Begin/End Support Functions (called from Begin/End)
19030// Docking: Settings
19031//-----------------------------------------------------------------------------
19032
19033//-----------------------------------------------------------------------------
19034// Typical Docking call flow: (root level is generally public API):
19035//-----------------------------------------------------------------------------
19036// - NewFrame() new dear imgui frame
19037// | DockContextNewFrameUpdateUndocking() - process queued undocking requests
19038// | - DockContextProcessUndockWindow() - process one window undocking request
19039// | - DockContextProcessUndockNode() - process one whole node undocking request
19040// | DockContextNewFrameUpdateUndocking() - process queue docking requests, create floating dock nodes
19041// | - update g.HoveredDockNode - [debug] update node hovered by mouse
19042// | - DockContextProcessDock() - process one docking request
19043// | - DockNodeUpdate()
19044// | - DockNodeUpdateForRootNode()
19045// | - DockNodeUpdateFlagsAndCollapse()
19046// | - DockNodeFindInfo()
19047// | - destroy unused node or tab bar
19048// | - create dock node host window
19049// | - Begin() etc.
19050// | - DockNodeStartMouseMovingWindow()
19051// | - DockNodeTreeUpdatePosSize()
19052// | - DockNodeTreeUpdateSplitter()
19053// | - draw node background
19054// | - DockNodeUpdateTabBar() - create/update tab bar for a docking node
19055// | - DockNodeAddTabBar()
19056// | - DockNodeWindowMenuUpdate()
19057// | - DockNodeCalcTabBarLayout()
19058// | - BeginTabBarEx()
19059// | - TabItemEx() calls
19060// | - EndTabBar()
19061// | - BeginDockableDragDropTarget()
19062// | - DockNodeUpdate() - recurse into child nodes...
19063//-----------------------------------------------------------------------------
19064// - DockSpace() user submit a dockspace into a window
19065// | Begin(Child) - create a child window
19066// | DockNodeUpdate() - call main dock node update function
19067// | End(Child)
19068// | ItemSize()
19069//-----------------------------------------------------------------------------
19070// - Begin()
19071// | BeginDocked()
19072// | BeginDockableDragDropSource()
19073// | BeginDockableDragDropTarget()
19074// | - DockNodePreviewDockRender()
19075//-----------------------------------------------------------------------------
19076// - EndFrame()
19077// | DockContextEndFrame()
19078//-----------------------------------------------------------------------------
19079
19080//-----------------------------------------------------------------------------
19081// Docking: Internal Types
19082//-----------------------------------------------------------------------------
19083// - ImGuiDockRequestType
19084// - ImGuiDockRequest
19085// - ImGuiDockPreviewData
19086// - ImGuiDockNodeSettings
19087// - ImGuiDockContext
19088//-----------------------------------------------------------------------------
19089
19090enum ImGuiDockRequestType
19091{
19092 ImGuiDockRequestType_None = 0,
19093 ImGuiDockRequestType_Dock,
19094 ImGuiDockRequestType_Undock,
19095 ImGuiDockRequestType_Split // Split is the same as Dock but without a DockPayload
19096};
19097
19099{
19100 ImGuiDockRequestType Type;
19101 ImGuiWindow *DockTargetWindow; // Destination/Target Window to dock into (may be a loose window or a DockNode, might
19102 // be NULL in which case DockTargetNode cannot be NULL)
19103 ImGuiDockNode *DockTargetNode; // Destination/Target Node to dock into
19104 ImGuiWindow *DockPayload; // Source/Payload window to dock (may be a loose window or a DockNode), [Optional]
19105 ImGuiDir DockSplitDir;
19106 float DockSplitRatio;
19107 bool DockSplitOuter;
19108 ImGuiWindow *UndockTargetWindow;
19109 ImGuiDockNode *UndockTargetNode;
19110
19112 {
19113 Type = ImGuiDockRequestType_None;
19114 DockTargetWindow = DockPayload = UndockTargetWindow = NULL;
19115 DockTargetNode = UndockTargetNode = NULL;
19116 DockSplitDir = ImGuiDir_None;
19117 DockSplitRatio = 0.5f;
19118 DockSplitOuter = false;
19119 }
19120};
19121
19123{
19124 ImGuiDockNode FutureNode;
19125 bool IsDropAllowed;
19126 bool IsCenterAvailable;
19127 bool IsSidesAvailable; // Hold your breath, grammar freaks..
19128 bool IsSplitDirExplicit; // Set when hovered the drop rect (vs. implicit SplitDir==None when hovered the window)
19129 ImGuiDockNode *SplitNode;
19130 ImGuiDir SplitDir;
19131 float SplitRatio;
19132 ImRect DropRectsDraw[ImGuiDir_COUNT +
19133 1]; // May be slightly different from hit-testing drop rects used in DockNodeCalcDropRects()
19134
19135 ImGuiDockPreviewData() : FutureNode(0)
19136 {
19137 IsDropAllowed = IsCenterAvailable = IsSidesAvailable = IsSplitDirExplicit = false;
19138 SplitNode = NULL;
19139 SplitDir = ImGuiDir_None;
19140 SplitRatio = 0.f;
19141 for (int n = 0; n < IM_ARRAYSIZE(DropRectsDraw); n++)
19142 DropRectsDraw[n] = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX);
19143 }
19144};
19145
19146// Persistent Settings data, stored contiguously in SettingsNodes (sizeof() ~32 bytes)
19148{
19149 ImGuiID ID;
19150 ImGuiID ParentNodeId;
19151 ImGuiID ParentWindowId;
19152 ImGuiID SelectedTabId;
19153 signed char SplitAxis;
19154 char Depth;
19155 ImGuiDockNodeFlags
19156 Flags; // NB: We save individual flags one by one in ascii format (ImGuiDockNodeFlags_SavedFlagsMask_)
19157 ImVec2ih Pos;
19158 ImVec2ih Size;
19159 ImVec2ih SizeRef;
19161 {
19162 memset(this, 0, sizeof(*this));
19163 SplitAxis = ImGuiAxis_None;
19164 }
19165};
19166
19167//-----------------------------------------------------------------------------
19168// Docking: Forward Declarations
19169//-----------------------------------------------------------------------------
19170
19171namespace ImGui
19172{
19173// ImGuiDockContext
19174static ImGuiDockNode *DockContextAddNode(ImGuiContext *ctx, ImGuiID id);
19175static void DockContextRemoveNode(ImGuiContext *ctx, ImGuiDockNode *node, bool merge_sibling_into_parent_node);
19176static void DockContextQueueNotifyRemovedNode(ImGuiContext *ctx, ImGuiDockNode *node);
19177static void DockContextProcessDock(ImGuiContext *ctx, ImGuiDockRequest *req);
19178static void DockContextPruneUnusedSettingsNodes(ImGuiContext *ctx);
19179static ImGuiDockNode *DockContextBindNodeToWindow(ImGuiContext *ctx, ImGuiWindow *window);
19180static void DockContextBuildNodesFromSettings(ImGuiContext *ctx, ImGuiDockNodeSettings *node_settings_array,
19181 int node_settings_count);
19182static void DockContextBuildAddWindowsToNodes(ImGuiContext *ctx, ImGuiID root_id); // Use root_id==0 to add all
19183
19184// ImGuiDockNode
19185static void DockNodeAddWindow(ImGuiDockNode *node, ImGuiWindow *window, bool add_to_tab_bar);
19186static void DockNodeMoveWindows(ImGuiDockNode *dst_node, ImGuiDockNode *src_node);
19187static void DockNodeMoveChildNodes(ImGuiDockNode *dst_node, ImGuiDockNode *src_node);
19188static ImGuiWindow *DockNodeFindWindowByID(ImGuiDockNode *node, ImGuiID id);
19189static void DockNodeApplyPosSizeToWindows(ImGuiDockNode *node);
19190static void DockNodeRemoveWindow(ImGuiDockNode *node, ImGuiWindow *window, ImGuiID save_dock_id);
19191static void DockNodeHideHostWindow(ImGuiDockNode *node);
19192static void DockNodeUpdate(ImGuiDockNode *node);
19193static void DockNodeUpdateForRootNode(ImGuiDockNode *node);
19194static void DockNodeUpdateFlagsAndCollapse(ImGuiDockNode *node);
19195static void DockNodeUpdateHasCentralNodeChild(ImGuiDockNode *node);
19196static void DockNodeUpdateTabBar(ImGuiDockNode *node, ImGuiWindow *host_window);
19197static void DockNodeAddTabBar(ImGuiDockNode *node);
19198static void DockNodeRemoveTabBar(ImGuiDockNode *node);
19199static void DockNodeWindowMenuUpdate(ImGuiDockNode *node, ImGuiTabBar *tab_bar);
19200static void DockNodeUpdateVisibleFlag(ImGuiDockNode *node);
19201static void DockNodeStartMouseMovingWindow(ImGuiDockNode *node, ImGuiWindow *window);
19202static bool DockNodeIsDropAllowed(ImGuiWindow *host_window, ImGuiWindow *payload_window);
19203static void DockNodePreviewDockSetup(ImGuiWindow *host_window, ImGuiDockNode *host_node, ImGuiWindow *payload_window,
19204 ImGuiDockNode *payload_node, ImGuiDockPreviewData *preview_data,
19205 bool is_explicit_target, bool is_outer_docking);
19206static void DockNodePreviewDockRender(ImGuiWindow *host_window, ImGuiDockNode *host_node, ImGuiWindow *payload_window,
19207 const ImGuiDockPreviewData *preview_data);
19208static void DockNodeCalcTabBarLayout(const ImGuiDockNode *node, ImRect *out_title_rect, ImRect *out_tab_bar_rect,
19209 ImVec2 *out_window_menu_button_pos, ImVec2 *out_close_button_pos);
19210static void DockNodeCalcSplitRects(ImVec2 &pos_old, ImVec2 &size_old, ImVec2 &pos_new, ImVec2 &size_new, ImGuiDir dir,
19211 ImVec2 size_new_desired);
19212static bool DockNodeCalcDropRectsAndTestMousePos(const ImRect &parent, ImGuiDir dir, ImRect &out_draw,
19213 bool outer_docking, ImVec2 *test_mouse_pos);
19214static const char *DockNodeGetHostWindowTitle(ImGuiDockNode *node, char *buf, int buf_size)
19215{
19216 ImFormatString(buf, buf_size, "##DockNode_%02X", node->ID);
19217 return buf;
19218}
19219static int DockNodeGetTabOrder(ImGuiWindow *window);
19220
19221// ImGuiDockNode tree manipulations
19222static void DockNodeTreeSplit(ImGuiContext *ctx, ImGuiDockNode *parent_node, ImGuiAxis split_axis,
19223 int split_first_child, float split_ratio, ImGuiDockNode *new_node);
19224static void DockNodeTreeMerge(ImGuiContext *ctx, ImGuiDockNode *parent_node, ImGuiDockNode *merge_lead_child);
19225static void DockNodeTreeUpdatePosSize(ImGuiDockNode *node, ImVec2 pos, ImVec2 size,
19226 ImGuiDockNode *only_write_to_single_node = NULL);
19227static void DockNodeTreeUpdateSplitter(ImGuiDockNode *node);
19228static ImGuiDockNode *DockNodeTreeFindVisibleNodeByPos(ImGuiDockNode *node, ImVec2 pos);
19229static ImGuiDockNode *DockNodeTreeFindFallbackLeafNode(ImGuiDockNode *node);
19230
19231// Settings
19232static void DockSettingsRenameNodeReferences(ImGuiID old_node_id, ImGuiID new_node_id);
19233static void DockSettingsRemoveNodeReferences(ImGuiID *node_ids, int node_ids_count);
19234static ImGuiDockNodeSettings *DockSettingsFindNodeSettings(ImGuiContext *ctx, ImGuiID node_id);
19235static void DockSettingsHandler_ClearAll(ImGuiContext *, ImGuiSettingsHandler *);
19236static void DockSettingsHandler_ApplyAll(ImGuiContext *, ImGuiSettingsHandler *);
19237static void *DockSettingsHandler_ReadOpen(ImGuiContext *, ImGuiSettingsHandler *, const char *name);
19238static void DockSettingsHandler_ReadLine(ImGuiContext *, ImGuiSettingsHandler *, void *entry, const char *line);
19239static void DockSettingsHandler_WriteAll(ImGuiContext *imgui_ctx, ImGuiSettingsHandler *handler, ImGuiTextBuffer *buf);
19240} // namespace ImGui
19241
19242//-----------------------------------------------------------------------------
19243// Docking: ImGuiDockContext
19244//-----------------------------------------------------------------------------
19245// The lifetime model is different from the one of regular windows: we always create a ImGuiDockNode for each
19246// ImGuiDockNodeSettings, or we always hold the entire docking node tree. Nodes are frequently hidden, e.g. if the
19247// window(s) or child nodes they host are not active. At boot time only, we run a simple GC to remove nodes that have no
19248// references. Because dock node settings (which are small, contiguous structures) are always mirrored by their
19249// corresponding dock nodes (more complete structures), we can also very easily recreate the nodes from scratch given
19250// the settings data (this is what DockContextRebuild() does). This is convenient as docking reconfiguration can be
19251// implemented by mostly poking at the simpler settings data.
19252//-----------------------------------------------------------------------------
19253// - DockContextInitialize()
19254// - DockContextShutdown()
19255// - DockContextClearNodes()
19256// - DockContextRebuildNodes()
19257// - DockContextNewFrameUpdateUndocking()
19258// - DockContextNewFrameUpdateDocking()
19259// - DockContextEndFrame()
19260// - DockContextFindNodeByID()
19261// - DockContextBindNodeToWindow()
19262// - DockContextGenNodeID()
19263// - DockContextAddNode()
19264// - DockContextRemoveNode()
19265// - ImGuiDockContextPruneNodeData
19266// - DockContextPruneUnusedSettingsNodes()
19267// - DockContextBuildNodesFromSettings()
19268// - DockContextBuildAddWindowsToNodes()
19269//-----------------------------------------------------------------------------
19270
19271void ImGui::DockContextInitialize(ImGuiContext *ctx)
19272{
19273 ImGuiContext &g = *ctx;
19274
19275 // Add .ini handle for persistent docking data
19276 ImGuiSettingsHandler ini_handler;
19277 ini_handler.TypeName = "Docking";
19278 ini_handler.TypeHash = ImHashStr("Docking");
19279 ini_handler.ClearAllFn = DockSettingsHandler_ClearAll;
19280 ini_handler.ReadInitFn = DockSettingsHandler_ClearAll; // Also clear on read
19281 ini_handler.ReadOpenFn = DockSettingsHandler_ReadOpen;
19282 ini_handler.ReadLineFn = DockSettingsHandler_ReadLine;
19283 ini_handler.ApplyAllFn = DockSettingsHandler_ApplyAll;
19284 ini_handler.WriteAllFn = DockSettingsHandler_WriteAll;
19285 g.SettingsHandlers.push_back(ini_handler);
19286
19287 g.DockNodeWindowMenuHandler = &DockNodeWindowMenuHandler_Default;
19288}
19289
19290void ImGui::DockContextShutdown(ImGuiContext *ctx)
19291{
19292 ImGuiDockContext *dc = &ctx->DockContext;
19293 for (int n = 0; n < dc->Nodes.Data.Size; n++)
19294 if (ImGuiDockNode *node = (ImGuiDockNode *)dc->Nodes.Data[n].val_p)
19295 IM_DELETE(node);
19296}
19297
19298void ImGui::DockContextClearNodes(ImGuiContext *ctx, ImGuiID root_id, bool clear_settings_refs)
19299{
19300 IM_UNUSED(ctx);
19301 IM_ASSERT(ctx == GImGui);
19302 DockBuilderRemoveNodeDockedWindows(root_id, clear_settings_refs);
19303 DockBuilderRemoveNodeChildNodes(root_id);
19304}
19305
19306// [DEBUG] This function also acts as a defacto test to make sure we can rebuild from scratch without a glitch
19307// (Different from DockSettingsHandler_ClearAll() + DockSettingsHandler_ApplyAll() because this reuses current
19308// settings!)
19309void ImGui::DockContextRebuildNodes(ImGuiContext *ctx)
19310{
19311 ImGuiContext &g = *ctx;
19312 ImGuiDockContext *dc = &ctx->DockContext;
19313 IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextRebuildNodes\n");
19314 SaveIniSettingsToMemory();
19315 ImGuiID root_id = 0; // Rebuild all
19316 DockContextClearNodes(ctx, root_id, false);
19317 DockContextBuildNodesFromSettings(ctx, dc->NodesSettings.Data, dc->NodesSettings.Size);
19318 DockContextBuildAddWindowsToNodes(ctx, root_id);
19319}
19320
19321// Docking context update function, called by NewFrame()
19322void ImGui::DockContextNewFrameUpdateUndocking(ImGuiContext *ctx)
19323{
19324 ImGuiContext &g = *ctx;
19325 ImGuiDockContext *dc = &ctx->DockContext;
19326 if (!(g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable))
19327 {
19328 if (dc->Nodes.Data.Size > 0 || dc->Requests.Size > 0)
19329 DockContextClearNodes(ctx, 0, true);
19330 return;
19331 }
19332
19333 // Setting NoSplit at runtime merges all nodes
19334 if (g.IO.ConfigDockingNoSplit)
19335 for (int n = 0; n < dc->Nodes.Data.Size; n++)
19336 if (ImGuiDockNode *node = (ImGuiDockNode *)dc->Nodes.Data[n].val_p)
19337 if (node->IsRootNode() && node->IsSplitNode())
19338 {
19339 DockBuilderRemoveNodeChildNodes(node->ID);
19340 // dc->WantFullRebuild = true;
19341 }
19342
19343 // Process full rebuild
19344#if 0
19345 if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_C)))
19346 dc->WantFullRebuild = true;
19347#endif
19348 if (dc->WantFullRebuild)
19349 {
19350 DockContextRebuildNodes(ctx);
19351 dc->WantFullRebuild = false;
19352 }
19353
19354 // Process Undocking requests (we need to process them _before_ the UpdateMouseMovingWindowNewFrame call in
19355 // NewFrame)
19356 for (ImGuiDockRequest &req : dc->Requests)
19357 {
19358 if (req.Type == ImGuiDockRequestType_Undock && req.UndockTargetWindow)
19359 DockContextProcessUndockWindow(ctx, req.UndockTargetWindow);
19360 else if (req.Type == ImGuiDockRequestType_Undock && req.UndockTargetNode)
19361 DockContextProcessUndockNode(ctx, req.UndockTargetNode);
19362 }
19363}
19364
19365// Docking context update function, called by NewFrame()
19366void ImGui::DockContextNewFrameUpdateDocking(ImGuiContext *ctx)
19367{
19368 ImGuiContext &g = *ctx;
19369 ImGuiDockContext *dc = &ctx->DockContext;
19370 if (!(g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable))
19371 return;
19372
19373 // [DEBUG] Store hovered dock node.
19374 // We could in theory use DockNodeTreeFindVisibleNodeByPos() on the root host dock node, but using ->DockNode is a
19375 // good shortcut. Note this is mostly a debug thing and isn't actually used for docking target, because docking
19376 // involve more detailed filtering.
19377 g.DebugHoveredDockNode = NULL;
19378 if (ImGuiWindow *hovered_window = g.HoveredWindowUnderMovingWindow)
19379 {
19380 if (hovered_window->DockNodeAsHost)
19381 g.DebugHoveredDockNode = DockNodeTreeFindVisibleNodeByPos(hovered_window->DockNodeAsHost, g.IO.MousePos);
19382 else if (hovered_window->RootWindow->DockNode)
19383 g.DebugHoveredDockNode = hovered_window->RootWindow->DockNode;
19384 }
19385
19386 // Process Docking requests
19387 for (ImGuiDockRequest &req : dc->Requests)
19388 if (req.Type == ImGuiDockRequestType_Dock)
19389 DockContextProcessDock(ctx, &req);
19390 dc->Requests.resize(0);
19391
19392 // Create windows for each automatic docking nodes
19393 // We can have NULL pointers when we delete nodes, but because ID are recycled this should amortize nicely (and our
19394 // node count will never be very high)
19395 for (int n = 0; n < dc->Nodes.Data.Size; n++)
19396 if (ImGuiDockNode *node = (ImGuiDockNode *)dc->Nodes.Data[n].val_p)
19397 if (node->IsFloatingNode())
19398 DockNodeUpdate(node);
19399}
19400
19401void ImGui::DockContextEndFrame(ImGuiContext *ctx)
19402{
19403 // Draw backgrounds of node missing their window
19404 ImGuiContext &g = *ctx;
19405 ImGuiDockContext *dc = &g.DockContext;
19406 for (int n = 0; n < dc->Nodes.Data.Size; n++)
19407 if (ImGuiDockNode *node = (ImGuiDockNode *)dc->Nodes.Data[n].val_p)
19408 if (node->LastFrameActive == g.FrameCount && node->IsVisible && node->HostWindow && node->IsLeafNode() &&
19409 !node->IsBgDrawnThisFrame)
19410 {
19411 ImRect bg_rect(node->Pos + ImVec2(0.0f, GetFrameHeight()), node->Pos + node->Size);
19412 ImDrawFlags bg_rounding_flags =
19413 CalcRoundingFlagsForRectInRect(bg_rect, node->HostWindow->Rect(), g.Style.DockingSeparatorSize);
19414 node->HostWindow->DrawList->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_BG);
19415 node->HostWindow->DrawList->AddRectFilled(bg_rect.Min, bg_rect.Max, node->LastBgColor,
19416 node->HostWindow->WindowRounding, bg_rounding_flags);
19417 }
19418}
19419
19420ImGuiDockNode *ImGui::DockContextFindNodeByID(ImGuiContext *ctx, ImGuiID id)
19421{
19422 return (ImGuiDockNode *)ctx->DockContext.Nodes.GetVoidPtr(id);
19423}
19424
19425ImGuiID ImGui::DockContextGenNodeID(ImGuiContext *ctx)
19426{
19427 // Generate an ID for new node (the exact ID value doesn't matter as long as it is not already used)
19428 // FIXME-OPT FIXME-DOCK: This is suboptimal, even if the node count is small enough not to be a worry.0
19429 // We should poke in ctx->Nodes to find a suitable ID faster. Even more so trivial that ctx->Nodes lookup is already
19430 // sorted.
19431 ImGuiID id = 0x0001;
19432 while (DockContextFindNodeByID(ctx, id) != NULL)
19433 id++;
19434 return id;
19435}
19436
19437static ImGuiDockNode *ImGui::DockContextAddNode(ImGuiContext *ctx, ImGuiID id)
19438{
19439 // Generate an ID for the new node (the exact ID value doesn't matter as long as it is not already used) and add the
19440 // first window.
19441 ImGuiContext &g = *ctx;
19442 if (id == 0)
19443 id = DockContextGenNodeID(ctx);
19444 else
19445 IM_ASSERT(DockContextFindNodeByID(ctx, id) == NULL);
19446
19447 // We don't set node->LastFrameAlive on construction. Nodes are always created at all time to reflect .ini settings!
19448 IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextAddNode 0x%08X\n", id);
19449 ImGuiDockNode *node = IM_NEW(ImGuiDockNode)(id);
19450 ctx->DockContext.Nodes.SetVoidPtr(node->ID, node);
19451 return node;
19452}
19453
19454static void ImGui::DockContextRemoveNode(ImGuiContext *ctx, ImGuiDockNode *node, bool merge_sibling_into_parent_node)
19455{
19456 ImGuiContext &g = *ctx;
19457 ImGuiDockContext *dc = &ctx->DockContext;
19458
19459 IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextRemoveNode 0x%08X\n", node->ID);
19460 IM_ASSERT(DockContextFindNodeByID(ctx, node->ID) == node);
19461 IM_ASSERT(node->ChildNodes[0] == NULL && node->ChildNodes[1] == NULL);
19462 IM_ASSERT(node->Windows.Size == 0);
19463
19464 if (node->HostWindow)
19465 node->HostWindow->DockNodeAsHost = NULL;
19466
19467 ImGuiDockNode *parent_node = node->ParentNode;
19468 const bool merge = (merge_sibling_into_parent_node && parent_node != NULL);
19469 if (merge)
19470 {
19471 IM_ASSERT(parent_node->ChildNodes[0] == node || parent_node->ChildNodes[1] == node);
19472 ImGuiDockNode *sibling_node =
19473 (parent_node->ChildNodes[0] == node ? parent_node->ChildNodes[1] : parent_node->ChildNodes[0]);
19474 DockNodeTreeMerge(&g, parent_node, sibling_node);
19475 }
19476 else
19477 {
19478 for (int n = 0; parent_node && n < IM_ARRAYSIZE(parent_node->ChildNodes); n++)
19479 if (parent_node->ChildNodes[n] == node)
19480 node->ParentNode->ChildNodes[n] = NULL;
19481 dc->Nodes.SetVoidPtr(node->ID, NULL);
19482 IM_DELETE(node);
19483 }
19484}
19485
19486static int IMGUI_CDECL DockNodeComparerDepthMostFirst(const void *lhs, const void *rhs)
19487{
19488 const ImGuiDockNode *a = *(const ImGuiDockNode *const *)lhs;
19489 const ImGuiDockNode *b = *(const ImGuiDockNode *const *)rhs;
19490 return ImGui::DockNodeGetDepth(b) - ImGui::DockNodeGetDepth(a);
19491}
19492
19493// Pre C++0x doesn't allow us to use a function-local type (without linkage) as template parameter, so we moved this
19494// here.
19496{
19497 int CountWindows, CountChildWindows, CountChildNodes;
19498 ImGuiID RootId;
19500 {
19501 CountWindows = CountChildWindows = CountChildNodes = 0;
19502 RootId = 0;
19503 }
19504};
19505
19506// Garbage collect unused nodes (run once at init time)
19507static void ImGui::DockContextPruneUnusedSettingsNodes(ImGuiContext *ctx)
19508{
19509 ImGuiContext &g = *ctx;
19510 ImGuiDockContext *dc = &ctx->DockContext;
19511 IM_ASSERT(g.Windows.Size == 0);
19512
19514 pool.Reserve(dc->NodesSettings.Size);
19515
19516 // Count child nodes and compute RootID
19517 for (int settings_n = 0; settings_n < dc->NodesSettings.Size; settings_n++)
19518 {
19519 ImGuiDockNodeSettings *settings = &dc->NodesSettings[settings_n];
19520 ImGuiDockContextPruneNodeData *parent_data = settings->ParentNodeId ? pool.GetByKey(settings->ParentNodeId) : 0;
19521 pool.GetOrAddByKey(settings->ID)->RootId = parent_data ? parent_data->RootId : settings->ID;
19522 if (settings->ParentNodeId)
19523 pool.GetOrAddByKey(settings->ParentNodeId)->CountChildNodes++;
19524 }
19525
19526 // Count reference to dock ids from dockspaces
19527 // We track the 'auto-DockNode <- manual-Window <- manual-DockSpace' in order to avoid 'auto-DockNode' being ditched
19528 // by DockContextPruneUnusedSettingsNodes()
19529 for (int settings_n = 0; settings_n < dc->NodesSettings.Size; settings_n++)
19530 {
19531 ImGuiDockNodeSettings *settings = &dc->NodesSettings[settings_n];
19532 if (settings->ParentWindowId != 0)
19533 if (ImGuiWindowSettings *window_settings = FindWindowSettingsByID(settings->ParentWindowId))
19534 if (window_settings->DockId)
19535 if (ImGuiDockContextPruneNodeData *data = pool.GetByKey(window_settings->DockId))
19536 data->CountChildNodes++;
19537 }
19538
19539 // Count reference to dock ids from window settings
19540 // We guard against the possibility of an invalid .ini file (RootID may point to a missing node)
19541 for (ImGuiWindowSettings *settings = g.SettingsWindows.begin(); settings != NULL;
19542 settings = g.SettingsWindows.next_chunk(settings))
19543 if (ImGuiID dock_id = settings->DockId)
19544 if (ImGuiDockContextPruneNodeData *data = pool.GetByKey(dock_id))
19545 {
19546 data->CountWindows++;
19547 if (ImGuiDockContextPruneNodeData *data_root =
19548 (data->RootId == dock_id) ? data : pool.GetByKey(data->RootId))
19549 data_root->CountChildWindows++;
19550 }
19551
19552 // Prune
19553 for (int settings_n = 0; settings_n < dc->NodesSettings.Size; settings_n++)
19554 {
19555 ImGuiDockNodeSettings *settings = &dc->NodesSettings[settings_n];
19556 ImGuiDockContextPruneNodeData *data = pool.GetByKey(settings->ID);
19557 if (data->CountWindows > 1)
19558 continue;
19559 ImGuiDockContextPruneNodeData *data_root = (data->RootId == settings->ID) ? data : pool.GetByKey(data->RootId);
19560
19561 bool remove = false;
19562 remove |= (data->CountWindows == 1 && settings->ParentNodeId == 0 && data->CountChildNodes == 0 &&
19563 !(settings->Flags & ImGuiDockNodeFlags_CentralNode)); // Floating root node with only 1 window
19564 remove |= (data->CountWindows == 0 && settings->ParentNodeId == 0 &&
19565 data->CountChildNodes == 0); // Leaf nodes with 0 window
19566 remove |= (data_root->CountChildWindows == 0);
19567 if (remove)
19568 {
19569 IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextPruneUnusedSettingsNodes: Prune 0x%08X\n", settings->ID);
19570 DockSettingsRemoveNodeReferences(&settings->ID, 1);
19571 settings->ID = 0;
19572 }
19573 }
19574}
19575
19576static void ImGui::DockContextBuildNodesFromSettings(ImGuiContext *ctx, ImGuiDockNodeSettings *node_settings_array,
19577 int node_settings_count)
19578{
19579 // Build nodes
19580 for (int node_n = 0; node_n < node_settings_count; node_n++)
19581 {
19582 ImGuiDockNodeSettings *settings = &node_settings_array[node_n];
19583 if (settings->ID == 0)
19584 continue;
19585 ImGuiDockNode *node = DockContextAddNode(ctx, settings->ID);
19586 node->ParentNode = settings->ParentNodeId ? DockContextFindNodeByID(ctx, settings->ParentNodeId) : NULL;
19587 node->Pos = ImVec2(settings->Pos.x, settings->Pos.y);
19588 node->Size = ImVec2(settings->Size.x, settings->Size.y);
19589 node->SizeRef = ImVec2(settings->SizeRef.x, settings->SizeRef.y);
19590 node->AuthorityForPos = node->AuthorityForSize = node->AuthorityForViewport = ImGuiDataAuthority_DockNode;
19591 if (node->ParentNode && node->ParentNode->ChildNodes[0] == NULL)
19592 node->ParentNode->ChildNodes[0] = node;
19593 else if (node->ParentNode && node->ParentNode->ChildNodes[1] == NULL)
19594 node->ParentNode->ChildNodes[1] = node;
19595 node->SelectedTabId = settings->SelectedTabId;
19596 node->SplitAxis = (ImGuiAxis)settings->SplitAxis;
19597 node->SetLocalFlags(settings->Flags & ImGuiDockNodeFlags_SavedFlagsMask_);
19598
19599 // Bind host window immediately if it already exist (in case of a rebuild)
19600 // This is useful as the RootWindowForTitleBarHighlight links necessary to highlight the currently focused node
19601 // requires node->HostWindow to be set.
19602 char host_window_title[20];
19603 ImGuiDockNode *root_node = DockNodeGetRootNode(node);
19604 node->HostWindow =
19605 FindWindowByName(DockNodeGetHostWindowTitle(root_node, host_window_title, IM_ARRAYSIZE(host_window_title)));
19606 }
19607}
19608
19609void ImGui::DockContextBuildAddWindowsToNodes(ImGuiContext *ctx, ImGuiID root_id)
19610{
19611 // Rebind all windows to nodes (they can also lazily rebind but we'll have a visible glitch during the first frame)
19612 ImGuiContext &g = *ctx;
19613 for (ImGuiWindow *window : g.Windows)
19614 {
19615 if (window->DockId == 0 || window->LastFrameActive < g.FrameCount - 1)
19616 continue;
19617 if (window->DockNode != NULL)
19618 continue;
19619
19620 ImGuiDockNode *node = DockContextFindNodeByID(ctx, window->DockId);
19621 IM_ASSERT(node != NULL); // This should have been called after DockContextBuildNodesFromSettings()
19622 if (root_id == 0 || DockNodeGetRootNode(node)->ID == root_id)
19623 DockNodeAddWindow(node, window, true);
19624 }
19625}
19626
19627//-----------------------------------------------------------------------------
19628// Docking: ImGuiDockContext Docking/Undocking functions
19629//-----------------------------------------------------------------------------
19630// - DockContextQueueDock()
19631// - DockContextQueueUndockWindow()
19632// - DockContextQueueUndockNode()
19633// - DockContextQueueNotifyRemovedNode()
19634// - DockContextProcessDock()
19635// - DockContextProcessUndockWindow()
19636// - DockContextProcessUndockNode()
19637// - DockContextCalcDropPosForDocking()
19638//-----------------------------------------------------------------------------
19639
19640void ImGui::DockContextQueueDock(ImGuiContext *ctx, ImGuiWindow *target, ImGuiDockNode *target_node,
19641 ImGuiWindow *payload, ImGuiDir split_dir, float split_ratio, bool split_outer)
19642{
19643 IM_ASSERT(target != payload);
19644 ImGuiDockRequest req;
19645 req.Type = ImGuiDockRequestType_Dock;
19646 req.DockTargetWindow = target;
19647 req.DockTargetNode = target_node;
19648 req.DockPayload = payload;
19649 req.DockSplitDir = split_dir;
19650 req.DockSplitRatio = split_ratio;
19651 req.DockSplitOuter = split_outer;
19652 ctx->DockContext.Requests.push_back(req);
19653}
19654
19655void ImGui::DockContextQueueUndockWindow(ImGuiContext *ctx, ImGuiWindow *window)
19656{
19657 ImGuiDockRequest req;
19658 req.Type = ImGuiDockRequestType_Undock;
19659 req.UndockTargetWindow = window;
19660 ctx->DockContext.Requests.push_back(req);
19661}
19662
19663void ImGui::DockContextQueueUndockNode(ImGuiContext *ctx, ImGuiDockNode *node)
19664{
19665 ImGuiDockRequest req;
19666 req.Type = ImGuiDockRequestType_Undock;
19667 req.UndockTargetNode = node;
19668 ctx->DockContext.Requests.push_back(req);
19669}
19670
19671void ImGui::DockContextQueueNotifyRemovedNode(ImGuiContext *ctx, ImGuiDockNode *node)
19672{
19673 ImGuiDockContext *dc = &ctx->DockContext;
19674 for (ImGuiDockRequest &req : dc->Requests)
19675 if (req.DockTargetNode == node)
19676 req.Type = ImGuiDockRequestType_None;
19677}
19678
19679void ImGui::DockContextProcessDock(ImGuiContext *ctx, ImGuiDockRequest *req)
19680{
19681 IM_ASSERT((req->Type == ImGuiDockRequestType_Dock && req->DockPayload != NULL) ||
19682 (req->Type == ImGuiDockRequestType_Split && req->DockPayload == NULL));
19683 IM_ASSERT(req->DockTargetWindow != NULL || req->DockTargetNode != NULL);
19684
19685 ImGuiContext &g = *ctx;
19686 IM_UNUSED(g);
19687
19688 ImGuiWindow *payload_window = req->DockPayload; // Optional
19689 ImGuiWindow *target_window = req->DockTargetWindow;
19690 ImGuiDockNode *node = req->DockTargetNode;
19691 if (payload_window)
19692 IMGUI_DEBUG_LOG_DOCKING(
19693 "[docking] DockContextProcessDock node 0x%08X target '%s' dock window '%s', split_dir %d\n",
19694 node ? node->ID : 0, target_window ? target_window->Name : "NULL", payload_window->Name, req->DockSplitDir);
19695 else
19696 IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextProcessDock node 0x%08X, split_dir %d\n", node ? node->ID : 0,
19697 req->DockSplitDir);
19698
19699 // Decide which Tab will be selected at the end of the operation
19700 ImGuiID next_selected_id = 0;
19701 ImGuiDockNode *payload_node = NULL;
19702 if (payload_window)
19703 {
19704 payload_node = payload_window->DockNodeAsHost;
19705 payload_window->DockNodeAsHost = NULL; // Important to clear this as the node will have its life as a child
19706 // which might be merged/deleted later.
19707 if (payload_node && payload_node->IsLeafNode())
19708 next_selected_id = payload_node->TabBar->NextSelectedTabId ? payload_node->TabBar->NextSelectedTabId
19709 : payload_node->TabBar->SelectedTabId;
19710 if (payload_node == NULL)
19711 next_selected_id = payload_window->TabId;
19712 }
19713
19714 // FIXME-DOCK: When we are trying to dock an existing single-window node into a loose window, transfer Node ID as
19715 // well When processing an interactive split, usually LastFrameAlive will be < g.FrameCount. But DockBuilder
19716 // operations can make it ==.
19717 if (node)
19718 IM_ASSERT(node->LastFrameAlive <= g.FrameCount);
19719 if (node && target_window && node == target_window->DockNodeAsHost)
19720 IM_ASSERT(node->Windows.Size > 0 || node->IsSplitNode() || node->IsCentralNode());
19721
19722 // Create new node and add existing window to it
19723 if (node == NULL)
19724 {
19725 node = DockContextAddNode(ctx, 0);
19726 node->Pos = target_window->Pos;
19727 node->Size = target_window->Size;
19728 if (target_window->DockNodeAsHost == NULL)
19729 {
19730 DockNodeAddWindow(node, target_window, true);
19731 node->TabBar->Tabs[0].Flags &= ~ImGuiTabItemFlags_Unsorted;
19732 target_window->DockIsActive = true;
19733 }
19734 }
19735
19736 ImGuiDir split_dir = req->DockSplitDir;
19737 if (split_dir != ImGuiDir_None)
19738 {
19739 // Split into two, one side will be our payload node unless we are dropping a loose window
19740 const ImGuiAxis split_axis =
19741 (split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Right) ? ImGuiAxis_X : ImGuiAxis_Y;
19742 const int split_inheritor_child_idx = (split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Up)
19743 ? 1
19744 : 0; // Current contents will be moved to the opposite side
19745 const float split_ratio = req->DockSplitRatio;
19746 DockNodeTreeSplit(ctx, node, split_axis, split_inheritor_child_idx, split_ratio,
19747 payload_node); // payload_node may be NULL here!
19748 ImGuiDockNode *new_node = node->ChildNodes[split_inheritor_child_idx ^ 1];
19749 new_node->HostWindow = node->HostWindow;
19750 node = new_node;
19751 }
19752 node->SetLocalFlags(node->LocalFlags & ~ImGuiDockNodeFlags_HiddenTabBar);
19753
19754 if (node != payload_node)
19755 {
19756 // Create tab bar before we call DockNodeMoveWindows (which would attempt to move the old tab-bar, which would
19757 // lead us to payload tabs wrongly appearing before target tabs!)
19758 if (node->Windows.Size > 0 && node->TabBar == NULL)
19759 {
19760 DockNodeAddTabBar(node);
19761 for (int n = 0; n < node->Windows.Size; n++)
19762 TabBarAddTab(node->TabBar, ImGuiTabItemFlags_None, node->Windows[n]);
19763 }
19764
19765 if (payload_node != NULL)
19766 {
19767 // Transfer full payload node (with 1+ child windows or child nodes)
19768 if (payload_node->IsSplitNode())
19769 {
19770 if (node->Windows.Size > 0)
19771 {
19772 // We can dock a split payload into a node that already has windows _only_ if our payload is a node
19773 // tree with a single visible node. In this situation, we move the windows of the target node into
19774 // the currently visible node of the payload. This allows us to preserve some of the underlying dock
19775 // tree settings nicely.
19776 IM_ASSERT(payload_node->OnlyNodeWithWindows !=
19777 NULL); // The docking should have been blocked by DockNodePreviewDockSetup() early on and
19778 // never submitted.
19779 ImGuiDockNode *visible_node = payload_node->OnlyNodeWithWindows;
19780 if (visible_node->TabBar)
19781 IM_ASSERT(visible_node->TabBar->Tabs.Size > 0);
19782 DockNodeMoveWindows(node, visible_node);
19783 DockNodeMoveWindows(visible_node, node);
19784 DockSettingsRenameNodeReferences(node->ID, visible_node->ID);
19785 }
19786 if (node->IsCentralNode())
19787 {
19788 // Central node property needs to be moved to a leaf node, pick the last focused one.
19789 // FIXME-DOCK: If we had to transfer other flags here, what would the policy be?
19790 ImGuiDockNode *last_focused_node = DockContextFindNodeByID(ctx, payload_node->LastFocusedNodeId);
19791 IM_ASSERT(last_focused_node != NULL);
19792 ImGuiDockNode *last_focused_root_node = DockNodeGetRootNode(last_focused_node);
19793 IM_ASSERT(last_focused_root_node == DockNodeGetRootNode(payload_node));
19794 last_focused_node->SetLocalFlags(last_focused_node->LocalFlags | ImGuiDockNodeFlags_CentralNode);
19795 node->SetLocalFlags(node->LocalFlags & ~ImGuiDockNodeFlags_CentralNode);
19796 last_focused_root_node->CentralNode = last_focused_node;
19797 }
19798
19799 IM_ASSERT(node->Windows.Size == 0);
19800 DockNodeMoveChildNodes(node, payload_node);
19801 }
19802 else
19803 {
19804 const ImGuiID payload_dock_id = payload_node->ID;
19805 DockNodeMoveWindows(node, payload_node);
19806 DockSettingsRenameNodeReferences(payload_dock_id, node->ID);
19807 }
19808 DockContextRemoveNode(ctx, payload_node, true);
19809 }
19810 else if (payload_window)
19811 {
19812 // Transfer single window
19813 const ImGuiID payload_dock_id = payload_window->DockId;
19814 node->VisibleWindow = payload_window;
19815 DockNodeAddWindow(node, payload_window, true);
19816 if (payload_dock_id != 0)
19817 DockSettingsRenameNodeReferences(payload_dock_id, node->ID);
19818 }
19819 }
19820 else
19821 {
19822 // When docking a floating single window node we want to reevaluate auto-hiding of the tab bar
19823 node->WantHiddenTabBarUpdate = true;
19824 }
19825
19826 // Update selection immediately
19827 if (ImGuiTabBar *tab_bar = node->TabBar)
19828 tab_bar->NextSelectedTabId = next_selected_id;
19829 MarkIniSettingsDirty();
19830}
19831
19832// Problem:
19833// Undocking a large (~full screen) window would leave it so large that the bottom right sizing corner would more
19834// than likely be off the screen and the window would be hard to resize to fit on screen. This can be particularly
19835// problematic with 'ConfigWindowsMoveFromTitleBarOnly=true' and/or with 'ConfigWindowsResizeFromEdges=false' as well
19836// (the later can be due to missing ImGuiBackendFlags_HasMouseCursors backend flag).
19837// Solution:
19838// When undocking a window we currently force its maximum size to 90% of the host viewport or monitor.
19839// Reevaluate this when we implement preserving docked/undocked size ("docking_wip/undocked_size" branch).
19840static ImVec2 FixLargeWindowsWhenUndocking(const ImVec2 &size, ImGuiViewport *ref_viewport)
19841{
19842 if (ref_viewport == NULL)
19843 return size;
19844
19845 ImGuiContext &g = *GImGui;
19846 ImVec2 max_size = ImTrunc(ref_viewport->WorkSize * 0.90f);
19847 if (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable)
19848 {
19849 const ImGuiPlatformMonitor *monitor = ImGui::GetViewportPlatformMonitor(ref_viewport);
19850 max_size = ImTrunc(monitor->WorkSize * 0.90f);
19851 }
19852 return ImMin(size, max_size);
19853}
19854
19855void ImGui::DockContextProcessUndockWindow(ImGuiContext *ctx, ImGuiWindow *window, bool clear_persistent_docking_ref)
19856{
19857 ImGuiContext &g = *ctx;
19858 IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextProcessUndockWindow window '%s', clear_persistent_docking_ref = %d\n",
19859 window->Name, clear_persistent_docking_ref);
19860 if (window->DockNode)
19861 DockNodeRemoveWindow(window->DockNode, window, clear_persistent_docking_ref ? 0 : window->DockId);
19862 else
19863 window->DockId = 0;
19864 window->Collapsed = false;
19865 window->DockIsActive = false;
19866 window->DockNodeIsVisible = window->DockTabIsVisible = false;
19867 window->Size = window->SizeFull = FixLargeWindowsWhenUndocking(window->SizeFull, window->Viewport);
19868
19869 MarkIniSettingsDirty();
19870}
19871
19872void ImGui::DockContextProcessUndockNode(ImGuiContext *ctx, ImGuiDockNode *node)
19873{
19874 ImGuiContext &g = *ctx;
19875 IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextProcessUndockNode node %08X\n", node->ID);
19876 IM_ASSERT(node->IsLeafNode());
19877 IM_ASSERT(node->Windows.Size >= 1);
19878
19879 if (node->IsRootNode() || node->IsCentralNode())
19880 {
19881 // In the case of a root node or central node, the node will have to stay in place. Create a new node to receive
19882 // the payload.
19883 ImGuiDockNode *new_node = DockContextAddNode(ctx, 0);
19884 new_node->Pos = node->Pos;
19885 new_node->Size = node->Size;
19886 new_node->SizeRef = node->SizeRef;
19887 DockNodeMoveWindows(new_node, node);
19888 DockSettingsRenameNodeReferences(node->ID, new_node->ID);
19889 node = new_node;
19890 }
19891 else
19892 {
19893 // Otherwise extract our node and merge our sibling back into the parent node.
19894 IM_ASSERT(node->ParentNode->ChildNodes[0] == node || node->ParentNode->ChildNodes[1] == node);
19895 int index_in_parent = (node->ParentNode->ChildNodes[0] == node) ? 0 : 1;
19896 node->ParentNode->ChildNodes[index_in_parent] = NULL;
19897 DockNodeTreeMerge(ctx, node->ParentNode, node->ParentNode->ChildNodes[index_in_parent ^ 1]);
19898 node->ParentNode->AuthorityForViewport =
19899 ImGuiDataAuthority_Window; // The node that stays in place keeps the viewport, so our newly dragged out node
19900 // will create a new viewport
19901 node->ParentNode = NULL;
19902 }
19903 for (ImGuiWindow *window : node->Windows)
19904 {
19905 window->Flags &= ~ImGuiWindowFlags_ChildWindow;
19906 if (window->ParentWindow)
19907 window->ParentWindow->DC.ChildWindows.find_erase(window);
19908 UpdateWindowParentAndRootLinks(window, window->Flags, NULL);
19909 }
19910 node->AuthorityForPos = node->AuthorityForSize = ImGuiDataAuthority_DockNode;
19911 node->Size = FixLargeWindowsWhenUndocking(node->Size, node->Windows[0]->Viewport);
19912 node->WantMouseMove = true;
19913 MarkIniSettingsDirty();
19914}
19915
19916// This is mostly used for automation.
19917bool ImGui::DockContextCalcDropPosForDocking(ImGuiWindow *target, ImGuiDockNode *target_node,
19918 ImGuiWindow *payload_window, ImGuiDockNode *payload_node,
19919 ImGuiDir split_dir, bool split_outer, ImVec2 *out_pos)
19920{
19921 if (target != NULL && target_node == NULL)
19922 target_node = target->DockNode;
19923
19924 // In DockNodePreviewDockSetup() for a root central node instead of showing both "inner" and "outer" drop rects
19925 // (which would be functionally identical) we only show the outer one. Reflect this here.
19926 if (target_node && target_node->ParentNode == NULL && target_node->IsCentralNode() && split_dir != ImGuiDir_None)
19927 split_outer = true;
19928 ImGuiDockPreviewData split_data;
19929 DockNodePreviewDockSetup(target, target_node, payload_window, payload_node, &split_data, false, split_outer);
19930 if (split_data.DropRectsDraw[split_dir + 1].IsInverted())
19931 return false;
19932 *out_pos = split_data.DropRectsDraw[split_dir + 1].GetCenter();
19933 return true;
19934}
19935
19936//-----------------------------------------------------------------------------
19937// Docking: ImGuiDockNode
19938//-----------------------------------------------------------------------------
19939// - DockNodeGetTabOrder()
19940// - DockNodeAddWindow()
19941// - DockNodeRemoveWindow()
19942// - DockNodeMoveChildNodes()
19943// - DockNodeMoveWindows()
19944// - DockNodeApplyPosSizeToWindows()
19945// - DockNodeHideHostWindow()
19946// - ImGuiDockNodeFindInfoResults
19947// - DockNodeFindInfo()
19948// - DockNodeFindWindowByID()
19949// - DockNodeUpdateFlagsAndCollapse()
19950// - DockNodeUpdateHasCentralNodeFlag()
19951// - DockNodeUpdateVisibleFlag()
19952// - DockNodeStartMouseMovingWindow()
19953// - DockNodeUpdate()
19954// - DockNodeUpdateWindowMenu()
19955// - DockNodeBeginAmendTabBar()
19956// - DockNodeEndAmendTabBar()
19957// - DockNodeUpdateTabBar()
19958// - DockNodeAddTabBar()
19959// - DockNodeRemoveTabBar()
19960// - DockNodeIsDropAllowedOne()
19961// - DockNodeIsDropAllowed()
19962// - DockNodeCalcTabBarLayout()
19963// - DockNodeCalcSplitRects()
19964// - DockNodeCalcDropRectsAndTestMousePos()
19965// - DockNodePreviewDockSetup()
19966// - DockNodePreviewDockRender()
19967//-----------------------------------------------------------------------------
19968
19969ImGuiDockNode::ImGuiDockNode(ImGuiID id)
19970{
19971 ID = id;
19972 SharedFlags = LocalFlags = LocalFlagsInWindows = MergedFlags = ImGuiDockNodeFlags_None;
19973 ParentNode = ChildNodes[0] = ChildNodes[1] = NULL;
19974 TabBar = NULL;
19975 SplitAxis = ImGuiAxis_None;
19976
19977 State = ImGuiDockNodeState_Unknown;
19978 LastBgColor = IM_COL32_WHITE;
19979 HostWindow = VisibleWindow = NULL;
19980 CentralNode = OnlyNodeWithWindows = NULL;
19981 CountNodeWithWindows = 0;
19982 LastFrameAlive = LastFrameActive = LastFrameFocused = -1;
19983 LastFocusedNodeId = 0;
19984 SelectedTabId = 0;
19985 WantCloseTabId = 0;
19986 RefViewportId = 0;
19987 AuthorityForPos = AuthorityForSize = ImGuiDataAuthority_DockNode;
19988 AuthorityForViewport = ImGuiDataAuthority_Auto;
19989 IsVisible = true;
19990 IsFocused = HasCloseButton = HasWindowMenuButton = HasCentralNodeChild = false;
19991 IsBgDrawnThisFrame = false;
19992 WantCloseAll = WantLockSizeOnce = WantMouseMove = WantHiddenTabBarUpdate = WantHiddenTabBarToggle = false;
19993}
19994
19995ImGuiDockNode::~ImGuiDockNode()
19996{
19997 IM_DELETE(TabBar);
19998 TabBar = NULL;
19999 ChildNodes[0] = ChildNodes[1] = NULL;
20000}
20001
20002int ImGui::DockNodeGetTabOrder(ImGuiWindow *window)
20003{
20004 ImGuiTabBar *tab_bar = window->DockNode->TabBar;
20005 if (tab_bar == NULL)
20006 return -1;
20007 ImGuiTabItem *tab = TabBarFindTabByID(tab_bar, window->TabId);
20008 return tab ? TabBarGetTabOrder(tab_bar, tab) : -1;
20009}
20010
20011static void DockNodeHideWindowDuringHostWindowCreation(ImGuiWindow *window)
20012{
20013 window->Hidden = true;
20014 window->HiddenFramesCanSkipItems = window->Active ? 1 : 2;
20015}
20016
20017static void ImGui::DockNodeAddWindow(ImGuiDockNode *node, ImGuiWindow *window, bool add_to_tab_bar)
20018{
20019 ImGuiContext &g = *GImGui;
20020 (void)g;
20021 if (window->DockNode)
20022 {
20023 // Can overwrite an existing window->DockNode (e.g. pointing to a disabled DockSpace node)
20024 IM_ASSERT(window->DockNode->ID != node->ID);
20025 DockNodeRemoveWindow(window->DockNode, window, 0);
20026 }
20027 IM_ASSERT(window->DockNode == NULL || window->DockNodeAsHost == NULL);
20028 IMGUI_DEBUG_LOG_DOCKING("[docking] DockNodeAddWindow node 0x%08X window '%s'\n", node->ID, window->Name);
20029
20030 // If more than 2 windows appeared on the same frame leading to the creation of a new hosting window,
20031 // we'll hide windows until the host window is ready. Hide the 1st window after its been output (so it is not
20032 // visible for one frame). We will call DockNodeHideWindowDuringHostWindowCreation() on ourselves in Begin()
20033 if (node->HostWindow == NULL && node->Windows.Size == 1 && node->Windows[0]->WasActive == false)
20034 DockNodeHideWindowDuringHostWindowCreation(node->Windows[0]);
20035
20036 node->Windows.push_back(window);
20037 node->WantHiddenTabBarUpdate = true;
20038 window->DockNode = node;
20039 window->DockId = node->ID;
20040 window->DockIsActive = (node->Windows.Size > 1);
20041 window->DockTabWantClose = false;
20042
20043 // When reactivating a node with one or two loose window, the window pos/size/viewport are authoritative over the
20044 // node storage. In particular it is important we init the viewport from the first window so we don't create two
20045 // viewports and drop one.
20046 if (node->HostWindow == NULL && node->IsFloatingNode())
20047 {
20048 if (node->AuthorityForPos == ImGuiDataAuthority_Auto)
20049 node->AuthorityForPos = ImGuiDataAuthority_Window;
20050 if (node->AuthorityForSize == ImGuiDataAuthority_Auto)
20051 node->AuthorityForSize = ImGuiDataAuthority_Window;
20052 if (node->AuthorityForViewport == ImGuiDataAuthority_Auto)
20053 node->AuthorityForViewport = ImGuiDataAuthority_Window;
20054 }
20055
20056 // Add to tab bar if requested
20057 if (add_to_tab_bar)
20058 {
20059 if (node->TabBar == NULL)
20060 {
20061 DockNodeAddTabBar(node);
20062 node->TabBar->SelectedTabId = node->TabBar->NextSelectedTabId = node->SelectedTabId;
20063
20064 // Add existing windows
20065 for (int n = 0; n < node->Windows.Size - 1; n++)
20066 TabBarAddTab(node->TabBar, ImGuiTabItemFlags_None, node->Windows[n]);
20067 }
20068 TabBarAddTab(node->TabBar, ImGuiTabItemFlags_Unsorted, window);
20069 }
20070
20071 DockNodeUpdateVisibleFlag(node);
20072
20073 // Update this without waiting for the next time we Begin() in the window, so our host window will have the proper
20074 // title bar color on its first frame.
20075 if (node->HostWindow)
20076 UpdateWindowParentAndRootLinks(window, window->Flags | ImGuiWindowFlags_ChildWindow, node->HostWindow);
20077}
20078
20079static void ImGui::DockNodeRemoveWindow(ImGuiDockNode *node, ImGuiWindow *window, ImGuiID save_dock_id)
20080{
20081 ImGuiContext &g = *GImGui;
20082 IM_ASSERT(window->DockNode == node);
20083 // IM_ASSERT(window->RootWindowDockTree == node->HostWindow);
20084 // IM_ASSERT(window->LastFrameActive < g.FrameCount); // We may call this from Begin()
20085 IM_ASSERT(save_dock_id == 0 || save_dock_id == node->ID);
20086 IMGUI_DEBUG_LOG_DOCKING("[docking] DockNodeRemoveWindow node 0x%08X window '%s'\n", node->ID, window->Name);
20087
20088 window->DockNode = NULL;
20089 window->DockIsActive = window->DockTabWantClose = false;
20090 window->DockId = save_dock_id;
20091 window->Flags &= ~ImGuiWindowFlags_ChildWindow;
20092 if (window->ParentWindow)
20093 window->ParentWindow->DC.ChildWindows.find_erase(window);
20094 UpdateWindowParentAndRootLinks(window, window->Flags, NULL); // Update immediately
20095
20096 if (node->HostWindow && node->HostWindow->ViewportOwned)
20097 {
20098 // When undocking from a user interaction this will always run in NewFrame() and have not much effect.
20099 // But mid-frame, if we clear viewport we need to mark window as hidden as well.
20100 window->Viewport = NULL;
20101 window->ViewportId = 0;
20102 window->ViewportOwned = false;
20103 window->Hidden = true;
20104 }
20105
20106 // Remove window
20107 bool erased = false;
20108 for (int n = 0; n < node->Windows.Size; n++)
20109 if (node->Windows[n] == window)
20110 {
20111 node->Windows.erase(node->Windows.Data + n);
20112 erased = true;
20113 break;
20114 }
20115 if (!erased)
20116 IM_ASSERT(erased);
20117 if (node->VisibleWindow == window)
20118 node->VisibleWindow = NULL;
20119
20120 // Remove tab and possibly tab bar
20121 node->WantHiddenTabBarUpdate = true;
20122 if (node->TabBar)
20123 {
20124 TabBarRemoveTab(node->TabBar, window->TabId);
20125 const int tab_count_threshold_for_tab_bar = node->IsCentralNode() ? 1 : 2;
20126 if (node->Windows.Size < tab_count_threshold_for_tab_bar)
20127 DockNodeRemoveTabBar(node);
20128 }
20129
20130 if (node->Windows.Size == 0 && !node->IsCentralNode() && !node->IsDockSpace() && window->DockId != node->ID)
20131 {
20132 // Automatic dock node delete themselves if they are not holding at least one tab
20133 DockContextRemoveNode(&g, node, true);
20134 return;
20135 }
20136
20137 if (node->Windows.Size == 1 && !node->IsCentralNode() && node->HostWindow)
20138 {
20139 ImGuiWindow *remaining_window = node->Windows[0];
20140 // Note: we used to transport viewport ownership here.
20141 remaining_window->Collapsed = node->HostWindow->Collapsed;
20142 }
20143
20144 // Update visibility immediately is required so the DockNodeUpdateRemoveInactiveChilds() processing can reflect
20145 // changes up the tree
20146 DockNodeUpdateVisibleFlag(node);
20147}
20148
20149static void ImGui::DockNodeMoveChildNodes(ImGuiDockNode *dst_node, ImGuiDockNode *src_node)
20150{
20151 IM_ASSERT(dst_node->Windows.Size == 0);
20152 dst_node->ChildNodes[0] = src_node->ChildNodes[0];
20153 dst_node->ChildNodes[1] = src_node->ChildNodes[1];
20154 if (dst_node->ChildNodes[0])
20155 dst_node->ChildNodes[0]->ParentNode = dst_node;
20156 if (dst_node->ChildNodes[1])
20157 dst_node->ChildNodes[1]->ParentNode = dst_node;
20158 dst_node->SplitAxis = src_node->SplitAxis;
20159 dst_node->SizeRef = src_node->SizeRef;
20160 src_node->ChildNodes[0] = src_node->ChildNodes[1] = NULL;
20161}
20162
20163static void ImGui::DockNodeMoveWindows(ImGuiDockNode *dst_node, ImGuiDockNode *src_node)
20164{
20165 // Insert tabs in the same orders as currently ordered (node->Windows isn't ordered)
20166 IM_ASSERT(src_node && dst_node && dst_node != src_node);
20167 ImGuiTabBar *src_tab_bar = src_node->TabBar;
20168 if (src_tab_bar != NULL)
20169 IM_ASSERT(src_node->Windows.Size <= src_node->TabBar->Tabs.Size);
20170
20171 // If the dst_node is empty we can just move the entire tab bar (to preserve selection, scrolling, etc.)
20172 bool move_tab_bar = (src_tab_bar != NULL) && (dst_node->TabBar == NULL);
20173 if (move_tab_bar)
20174 {
20175 dst_node->TabBar = src_node->TabBar;
20176 src_node->TabBar = NULL;
20177 }
20178
20179 // Tab order is not important here, it is preserved by sorting in DockNodeUpdateTabBar().
20180 for (ImGuiWindow *window : src_node->Windows)
20181 {
20182 window->DockNode = NULL;
20183 window->DockIsActive = false;
20184 DockNodeAddWindow(dst_node, window, !move_tab_bar);
20185 }
20186 src_node->Windows.clear();
20187
20188 if (!move_tab_bar && src_node->TabBar)
20189 {
20190 if (dst_node->TabBar)
20191 dst_node->TabBar->SelectedTabId = src_node->TabBar->SelectedTabId;
20192 DockNodeRemoveTabBar(src_node);
20193 }
20194}
20195
20196static void ImGui::DockNodeApplyPosSizeToWindows(ImGuiDockNode *node)
20197{
20198 for (ImGuiWindow *window : node->Windows)
20199 {
20200 SetWindowPos(window, node->Pos, ImGuiCond_Always); // We don't assign directly to Pos because it can break the
20201 // calculation of SizeContents on next frame
20202 SetWindowSize(window, node->Size, ImGuiCond_Always);
20203 }
20204}
20205
20206static void ImGui::DockNodeHideHostWindow(ImGuiDockNode *node)
20207{
20208 if (node->HostWindow)
20209 {
20210 if (node->HostWindow->DockNodeAsHost == node)
20211 node->HostWindow->DockNodeAsHost = NULL;
20212 node->HostWindow = NULL;
20213 }
20214
20215 if (node->Windows.Size == 1)
20216 {
20217 node->VisibleWindow = node->Windows[0];
20218 node->Windows[0]->DockIsActive = false;
20219 }
20220
20221 if (node->TabBar)
20222 DockNodeRemoveTabBar(node);
20223}
20224
20225// Search function called once by root node in DockNodeUpdate()
20227{
20228 ImGuiDockNode *CentralNode;
20229 ImGuiDockNode *FirstNodeWithWindows;
20230 int CountNodesWithWindows;
20231 // ImGuiWindowClass WindowClassForMerges;
20232
20234 {
20235 memset(this, 0, sizeof(*this));
20236 }
20237};
20238
20239static void DockNodeFindInfo(ImGuiDockNode *node, ImGuiDockNodeTreeInfo *info)
20240{
20241 if (node->Windows.Size > 0)
20242 {
20243 if (info->FirstNodeWithWindows == NULL)
20244 info->FirstNodeWithWindows = node;
20245 info->CountNodesWithWindows++;
20246 }
20247 if (node->IsCentralNode())
20248 {
20249 IM_ASSERT(info->CentralNode == NULL); // Should be only one
20250 IM_ASSERT(node->IsLeafNode() &&
20251 "If you get this assert: please submit .ini file + repro of actions leading to this.");
20252 info->CentralNode = node;
20253 }
20254 if (info->CountNodesWithWindows > 1 && info->CentralNode != NULL)
20255 return;
20256 if (node->ChildNodes[0])
20257 DockNodeFindInfo(node->ChildNodes[0], info);
20258 if (node->ChildNodes[1])
20259 DockNodeFindInfo(node->ChildNodes[1], info);
20260}
20261
20262static ImGuiWindow *ImGui::DockNodeFindWindowByID(ImGuiDockNode *node, ImGuiID id)
20263{
20264 IM_ASSERT(id != 0);
20265 for (ImGuiWindow *window : node->Windows)
20266 if (window->ID == id)
20267 return window;
20268 return NULL;
20269}
20270
20271// - Remove inactive windows/nodes.
20272// - Update visibility flag.
20273static void ImGui::DockNodeUpdateFlagsAndCollapse(ImGuiDockNode *node)
20274{
20275 ImGuiContext &g = *GImGui;
20276 IM_ASSERT(node->ParentNode == NULL || node->ParentNode->ChildNodes[0] == node ||
20277 node->ParentNode->ChildNodes[1] == node);
20278
20279 // Inherit most flags
20280 if (node->ParentNode)
20281 node->SharedFlags = node->ParentNode->SharedFlags & ImGuiDockNodeFlags_SharedFlagsInheritMask_;
20282
20283 // Recurse into children
20284 // There is the possibility that one of our child becoming empty will delete itself and moving its sibling contents
20285 // into 'node'. If 'node->ChildNode[0]' delete itself, then 'node->ChildNode[1]->Windows' will be moved into 'node'
20286 // If 'node->ChildNode[1]' delete itself, then 'node->ChildNode[0]->Windows' will be moved into 'node' and the
20287 // "remove inactive windows" loop will have run twice on those windows (harmless)
20288 node->HasCentralNodeChild = false;
20289 if (node->ChildNodes[0])
20290 DockNodeUpdateFlagsAndCollapse(node->ChildNodes[0]);
20291 if (node->ChildNodes[1])
20292 DockNodeUpdateFlagsAndCollapse(node->ChildNodes[1]);
20293
20294 // Remove inactive windows, collapse nodes
20295 // Merge node flags overrides stored in windows
20296 node->LocalFlagsInWindows = ImGuiDockNodeFlags_None;
20297 for (int window_n = 0; window_n < node->Windows.Size; window_n++)
20298 {
20299 ImGuiWindow *window = node->Windows[window_n];
20300 IM_ASSERT(window->DockNode == node);
20301
20302 bool node_was_active = (node->LastFrameActive + 1 == g.FrameCount);
20303 bool remove = false;
20304 remove |= node_was_active && (window->LastFrameActive + 1 < g.FrameCount);
20305 remove |= node_was_active && (node->WantCloseAll || node->WantCloseTabId == window->TabId) &&
20306 window->HasCloseButton &&
20307 !(window->Flags & ImGuiWindowFlags_UnsavedDocument); // Submit all _expected_ closure from last frame
20308 remove |= (window->DockTabWantClose);
20309 if (remove)
20310 {
20311 window->DockTabWantClose = false;
20312 if (node->Windows.Size == 1 && !node->IsCentralNode())
20313 {
20314 DockNodeHideHostWindow(node);
20315 node->State = ImGuiDockNodeState_HostWindowHiddenBecauseSingleWindow;
20316 DockNodeRemoveWindow(node, window, node->ID); // Will delete the node so it'll be invalid on return
20317 return;
20318 }
20319 DockNodeRemoveWindow(node, window, node->ID);
20320 window_n--;
20321 continue;
20322 }
20323
20324 // FIXME-DOCKING: Missing policies for conflict resolution, hence the "Experimental" tag on this.
20325 // node->LocalFlagsInWindow &= ~window->WindowClass.DockNodeFlagsOverrideClear;
20326 node->LocalFlagsInWindows |= window->WindowClass.DockNodeFlagsOverrideSet;
20327 }
20328 node->UpdateMergedFlags();
20329
20330 // Auto-hide tab bar option
20331 ImGuiDockNodeFlags node_flags = node->MergedFlags;
20332 if (node->WantHiddenTabBarUpdate && node->Windows.Size == 1 && (node_flags & ImGuiDockNodeFlags_AutoHideTabBar) &&
20333 !node->IsHiddenTabBar())
20334 node->WantHiddenTabBarToggle = true;
20335 node->WantHiddenTabBarUpdate = false;
20336
20337 // Cancel toggling if we know our tab bar is enforced to be hidden at all times
20338 if (node->WantHiddenTabBarToggle && node->VisibleWindow &&
20339 (node->VisibleWindow->WindowClass.DockNodeFlagsOverrideSet & ImGuiDockNodeFlags_HiddenTabBar))
20340 node->WantHiddenTabBarToggle = false;
20341
20342 // Apply toggles at a single point of the frame (here!)
20343 if (node->Windows.Size > 1)
20344 node->SetLocalFlags(node->LocalFlags & ~ImGuiDockNodeFlags_HiddenTabBar);
20345 else if (node->WantHiddenTabBarToggle)
20346 node->SetLocalFlags(node->LocalFlags ^ ImGuiDockNodeFlags_HiddenTabBar);
20347 node->WantHiddenTabBarToggle = false;
20348
20349 DockNodeUpdateVisibleFlag(node);
20350}
20351
20352// This is rarely called as DockNodeUpdateForRootNode() generally does it most frames.
20353static void ImGui::DockNodeUpdateHasCentralNodeChild(ImGuiDockNode *node)
20354{
20355 node->HasCentralNodeChild = false;
20356 if (node->ChildNodes[0])
20357 DockNodeUpdateHasCentralNodeChild(node->ChildNodes[0]);
20358 if (node->ChildNodes[1])
20359 DockNodeUpdateHasCentralNodeChild(node->ChildNodes[1]);
20360 if (node->IsRootNode())
20361 {
20362 ImGuiDockNode *mark_node = node->CentralNode;
20363 while (mark_node)
20364 {
20365 mark_node->HasCentralNodeChild = true;
20366 mark_node = mark_node->ParentNode;
20367 }
20368 }
20369}
20370
20371static void ImGui::DockNodeUpdateVisibleFlag(ImGuiDockNode *node)
20372{
20373 // Update visibility flag
20374 bool is_visible = (node->ParentNode == NULL) ? node->IsDockSpace() : node->IsCentralNode();
20375 is_visible |= (node->Windows.Size > 0);
20376 is_visible |= (node->ChildNodes[0] && node->ChildNodes[0]->IsVisible);
20377 is_visible |= (node->ChildNodes[1] && node->ChildNodes[1]->IsVisible);
20378 node->IsVisible = is_visible;
20379}
20380
20381static void ImGui::DockNodeStartMouseMovingWindow(ImGuiDockNode *node, ImGuiWindow *window)
20382{
20383 ImGuiContext &g = *GImGui;
20384 IM_ASSERT(node->WantMouseMove == true);
20385 StartMouseMovingWindow(window);
20386 g.ActiveIdClickOffset = g.IO.MouseClickedPos[0] - node->Pos;
20387 g.MovingWindow = window; // If we are docked into a non moveable root window, StartMouseMovingWindow() won't set
20388 // g.MovingWindow. Override that decision.
20389 node->WantMouseMove = false;
20390}
20391
20392// Update CentralNode, OnlyNodeWithWindows, LastFocusedNodeID. Copy window class.
20393static void ImGui::DockNodeUpdateForRootNode(ImGuiDockNode *node)
20394{
20395 DockNodeUpdateFlagsAndCollapse(node);
20396
20397 // - Setup central node pointers
20398 // - Find if there's only a single visible window in the hierarchy (in which case we need to display a regular title
20399 // bar -> FIXME-DOCK: that last part is not done yet!) Cannot merge this with DockNodeUpdateFlagsAndCollapse()
20400 // because FirstNodeWithWindows is found after window removal and child collapsing
20402 DockNodeFindInfo(node, &info);
20403 node->CentralNode = info.CentralNode;
20404 node->OnlyNodeWithWindows = (info.CountNodesWithWindows == 1) ? info.FirstNodeWithWindows : NULL;
20405 node->CountNodeWithWindows = info.CountNodesWithWindows;
20406 if (node->LastFocusedNodeId == 0 && info.FirstNodeWithWindows != NULL)
20407 node->LastFocusedNodeId = info.FirstNodeWithWindows->ID;
20408
20409 // Copy the window class from of our first window so it can be used for proper dock filtering.
20410 // When node has mixed windows, prioritize the class with the most constraint (DockingAllowUnclassed = false) as the
20411 // reference to copy.
20412 // FIXME-DOCK: We don't recurse properly, this code could be reworked to work from DockNodeUpdateScanRec.
20413 if (ImGuiDockNode *first_node_with_windows = info.FirstNodeWithWindows)
20414 {
20415 node->WindowClass = first_node_with_windows->Windows[0]->WindowClass;
20416 for (int n = 1; n < first_node_with_windows->Windows.Size; n++)
20417 if (first_node_with_windows->Windows[n]->WindowClass.DockingAllowUnclassed == false)
20418 {
20419 node->WindowClass = first_node_with_windows->Windows[n]->WindowClass;
20420 break;
20421 }
20422 }
20423
20424 ImGuiDockNode *mark_node = node->CentralNode;
20425 while (mark_node)
20426 {
20427 mark_node->HasCentralNodeChild = true;
20428 mark_node = mark_node->ParentNode;
20429 }
20430}
20431
20432static void DockNodeSetupHostWindow(ImGuiDockNode *node, ImGuiWindow *host_window)
20433{
20434 // Remove ourselves from any previous different host window
20435 // This can happen if a user mistakenly does (see #4295 for details):
20436 // - N+0: DockBuilderAddNode(id, 0) // missing ImGuiDockNodeFlags_DockSpace
20437 // - N+1: NewFrame() // will create floating host window for that node
20438 // - N+1: DockSpace(id) // requalify node as dockspace, moving host window
20439 if (node->HostWindow && node->HostWindow != host_window && node->HostWindow->DockNodeAsHost == node)
20440 node->HostWindow->DockNodeAsHost = NULL;
20441
20442 host_window->DockNodeAsHost = node;
20443 node->HostWindow = host_window;
20444}
20445
20446static void ImGui::DockNodeUpdate(ImGuiDockNode *node)
20447{
20448 ImGuiContext &g = *GImGui;
20449 IM_ASSERT(node->LastFrameActive != g.FrameCount);
20450 node->LastFrameAlive = g.FrameCount;
20451 node->IsBgDrawnThisFrame = false;
20452
20453 node->CentralNode = node->OnlyNodeWithWindows = NULL;
20454 if (node->IsRootNode())
20455 DockNodeUpdateForRootNode(node);
20456
20457 // Remove tab bar if not needed
20458 if (node->TabBar && node->IsNoTabBar())
20459 DockNodeRemoveTabBar(node);
20460
20461 // Early out for hidden root dock nodes (when all DockId references are in inactive windows, or there is only 1
20462 // floating window holding on the DockId)
20463 bool want_to_hide_host_window = false;
20464 if (node->IsFloatingNode())
20465 {
20466 if (node->Windows.Size <= 1 && node->IsLeafNode())
20467 if (!g.IO.ConfigDockingAlwaysTabBar &&
20468 (node->Windows.Size == 0 || !node->Windows[0]->WindowClass.DockingAlwaysTabBar))
20469 want_to_hide_host_window = true;
20470 if (node->CountNodeWithWindows == 0)
20471 want_to_hide_host_window = true;
20472 }
20473 if (want_to_hide_host_window)
20474 {
20475 if (node->Windows.Size == 1)
20476 {
20477 // Floating window pos/size is authoritative
20478 ImGuiWindow *single_window = node->Windows[0];
20479 node->Pos = single_window->Pos;
20480 node->Size = single_window->SizeFull;
20481 node->AuthorityForPos = node->AuthorityForSize = node->AuthorityForViewport = ImGuiDataAuthority_Window;
20482
20483 // Transfer focus immediately so when we revert to a regular window it is immediately selected
20484 if (node->HostWindow && g.NavWindow == node->HostWindow)
20485 FocusWindow(single_window);
20486 if (node->HostWindow)
20487 {
20488 IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Node %08X transfer Viewport %08X->%08X to Window '%s'\n", node->ID,
20489 node->HostWindow->Viewport->ID, single_window->ID, single_window->Name);
20490 single_window->Viewport = node->HostWindow->Viewport;
20491 single_window->ViewportId = node->HostWindow->ViewportId;
20492 if (node->HostWindow->ViewportOwned)
20493 {
20494 single_window->Viewport->ID = single_window->ID;
20495 single_window->Viewport->Window = single_window;
20496 single_window->ViewportOwned = true;
20497 }
20498 }
20499 node->RefViewportId = single_window->ViewportId;
20500 }
20501
20502 DockNodeHideHostWindow(node);
20503 node->State = ImGuiDockNodeState_HostWindowHiddenBecauseSingleWindow;
20504 node->WantCloseAll = false;
20505 node->WantCloseTabId = 0;
20506 node->HasCloseButton = node->HasWindowMenuButton = false;
20507 node->LastFrameActive = g.FrameCount;
20508
20509 if (node->WantMouseMove && node->Windows.Size == 1)
20510 DockNodeStartMouseMovingWindow(node, node->Windows[0]);
20511 return;
20512 }
20513
20514 // In some circumstance we will defer creating the host window (so everything will be kept hidden),
20515 // while the expected visible window is resizing itself.
20516 // This is important for first-time (no ini settings restored) single window when io.ConfigDockingAlwaysTabBar is
20517 // enabled, otherwise the node ends up using the minimum window size. Effectively those windows will take an extra
20518 // frame to show up:
20519 // N+0: Begin(): window created (with no known size), node is created
20520 // N+1: DockNodeUpdate(): node skip creating host window / Begin(): window size applied, not visible
20521 // N+2: DockNodeUpdate(): node can create host window / Begin(): window becomes visible
20522 // We could remove this frame if we could reliably calculate the expected window size during node update, before the
20523 // Begin() code. It would require a generalization of CalcWindowExpectedSize(), probably extracting code away from
20524 // Begin(). In reality it isn't very important as user quickly ends up with size data in .ini file.
20525 if (node->IsVisible && node->HostWindow == NULL && node->IsFloatingNode() && node->IsLeafNode())
20526 {
20527 IM_ASSERT(node->Windows.Size > 0);
20528 ImGuiWindow *ref_window = NULL;
20529 if (node->SelectedTabId !=
20530 0) // Note that we prune single-window-node settings on .ini loading, so this is generally 0 for them!
20531 ref_window = DockNodeFindWindowByID(node, node->SelectedTabId);
20532 if (ref_window == NULL)
20533 ref_window = node->Windows[0];
20534 if (ref_window->AutoFitFramesX > 0 || ref_window->AutoFitFramesY > 0)
20535 {
20536 node->State = ImGuiDockNodeState_HostWindowHiddenBecauseWindowsAreResizing;
20537 return;
20538 }
20539 }
20540
20541 const ImGuiDockNodeFlags node_flags = node->MergedFlags;
20542
20543 // Decide if the node will have a close button and a window menu button
20544 node->HasWindowMenuButton = (node->Windows.Size > 0) && (node_flags & ImGuiDockNodeFlags_NoWindowMenuButton) == 0;
20545 node->HasCloseButton = false;
20546 for (ImGuiWindow *window : node->Windows)
20547 {
20548 // FIXME-DOCK: Setting DockIsActive here means that for single active window in a leaf node, DockIsActive will
20549 // be cleared until the next Begin() call.
20550 node->HasCloseButton |= window->HasCloseButton;
20551 window->DockIsActive = (node->Windows.Size > 1);
20552 }
20553 if (node_flags & ImGuiDockNodeFlags_NoCloseButton)
20554 node->HasCloseButton = false;
20555
20556 // Bind or create host window
20557 ImGuiWindow *host_window = NULL;
20558 bool beginned_into_host_window = false;
20559 if (node->IsDockSpace())
20560 {
20561 // [Explicit root dockspace node]
20562 IM_ASSERT(node->HostWindow);
20563 host_window = node->HostWindow;
20564 }
20565 else
20566 {
20567 // [Automatic root or child nodes]
20568 if (node->IsRootNode() && node->IsVisible)
20569 {
20570 ImGuiWindow *ref_window = (node->Windows.Size > 0) ? node->Windows[0] : NULL;
20571
20572 // Sync Pos
20573 if (node->AuthorityForPos == ImGuiDataAuthority_Window && ref_window)
20574 SetNextWindowPos(ref_window->Pos);
20575 else if (node->AuthorityForPos == ImGuiDataAuthority_DockNode)
20576 SetNextWindowPos(node->Pos);
20577
20578 // Sync Size
20579 if (node->AuthorityForSize == ImGuiDataAuthority_Window && ref_window)
20580 SetNextWindowSize(ref_window->SizeFull);
20581 else if (node->AuthorityForSize == ImGuiDataAuthority_DockNode)
20582 SetNextWindowSize(node->Size);
20583
20584 // Sync Collapsed
20585 if (node->AuthorityForSize == ImGuiDataAuthority_Window && ref_window)
20586 SetNextWindowCollapsed(ref_window->Collapsed);
20587
20588 // Sync Viewport
20589 if (node->AuthorityForViewport == ImGuiDataAuthority_Window && ref_window)
20590 SetNextWindowViewport(ref_window->ViewportId);
20591 else if (node->AuthorityForViewport == ImGuiDataAuthority_Window && node->RefViewportId != 0)
20592 SetNextWindowViewport(node->RefViewportId);
20593
20594 SetNextWindowClass(&node->WindowClass);
20595
20596 // Begin into the host window
20597 char window_label[20];
20598 DockNodeGetHostWindowTitle(node, window_label, IM_ARRAYSIZE(window_label));
20599 ImGuiWindowFlags window_flags =
20600 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_DockNodeHost;
20601 window_flags |= ImGuiWindowFlags_NoFocusOnAppearing;
20602 window_flags |=
20603 ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoCollapse;
20604 window_flags |= ImGuiWindowFlags_NoTitleBar;
20605
20606 SetNextWindowBgAlpha(0.0f); // Don't set ImGuiWindowFlags_NoBackground because it disables borders
20607 PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
20608 Begin(window_label, NULL, window_flags);
20609 PopStyleVar();
20610 beginned_into_host_window = true;
20611
20612 host_window = g.CurrentWindow;
20613 DockNodeSetupHostWindow(node, host_window);
20614 host_window->DC.CursorPos = host_window->Pos;
20615 node->Pos = host_window->Pos;
20616 node->Size = host_window->Size;
20617
20618 // We set ImGuiWindowFlags_NoFocusOnAppearing because we don't want the host window to take full focus (e.g.
20619 // steal NavWindow) But we still it bring it to the front of display. There's no way to choose this precise
20620 // behavior via window flags. One simple case to ponder if: window A has a toggle to create windows B/C/D.
20621 // Dock B/C/D together, clear the toggle and enable it again. When reappearing B/C/D will request focus and
20622 // be moved to the top of the display pile, but they are not linked to the dock host window during the frame
20623 // they appear. The dock host window would keep its old display order, and the sorting in EndFrame would
20624 // move B/C/D back after the dock host window, losing their top-most status.
20625 if (node->HostWindow->Appearing)
20626 BringWindowToDisplayFront(node->HostWindow);
20627
20628 node->AuthorityForPos = node->AuthorityForSize = node->AuthorityForViewport = ImGuiDataAuthority_Auto;
20629 }
20630 else if (node->ParentNode)
20631 {
20632 node->HostWindow = host_window = node->ParentNode->HostWindow;
20633 node->AuthorityForPos = node->AuthorityForSize = node->AuthorityForViewport = ImGuiDataAuthority_Auto;
20634 }
20635 if (node->WantMouseMove && node->HostWindow)
20636 DockNodeStartMouseMovingWindow(node, node->HostWindow);
20637 }
20638 node->RefViewportId = 0; // Clear when we have a host window
20639
20640 // Update focused node (the one whose title bar is highlight) within a node tree
20641 if (node->IsSplitNode())
20642 IM_ASSERT(node->TabBar == NULL);
20643 if (node->IsRootNode())
20644 if (ImGuiWindow *p_window = g.NavWindow ? g.NavWindow->RootWindow : NULL)
20645 while (p_window != NULL && p_window->DockNode != NULL)
20646 {
20647 ImGuiDockNode *p_node = DockNodeGetRootNode(p_window->DockNode);
20648 if (p_node == node)
20649 {
20650 node->LastFocusedNodeId = p_window->DockNode->ID; // Note: not using root node ID!
20651 break;
20652 }
20653 p_window = p_node->HostWindow ? p_node->HostWindow->RootWindow : NULL;
20654 }
20655
20656 // Register a hit-test hole in the window unless we are currently dragging a window that is compatible with our
20657 // dockspace
20658 ImGuiDockNode *central_node = node->CentralNode;
20659 const bool central_node_hole = node->IsRootNode() && host_window &&
20660 (node_flags & ImGuiDockNodeFlags_PassthruCentralNode) != 0 && central_node != NULL &&
20661 central_node->IsEmpty();
20662 bool central_node_hole_register_hit_test_hole = central_node_hole;
20663 if (central_node_hole)
20664 if (const ImGuiPayload *payload = ImGui::GetDragDropPayload())
20665 if (payload->IsDataType(IMGUI_PAYLOAD_TYPE_WINDOW) &&
20666 DockNodeIsDropAllowed(host_window, *(ImGuiWindow **)payload->Data))
20667 central_node_hole_register_hit_test_hole = false;
20668 if (central_node_hole_register_hit_test_hole)
20669 {
20670 // We add a little padding to match the "resize from edges" behavior and allow grabbing the splitter easily.
20671 // (But we only add it if there's something else on the other side of the hole, otherwise for e.g. fullscreen
20672 // covering passthru node we'd have a gap on the edge not covered by the hole)
20673 IM_ASSERT(node->IsDockSpace()); // We cannot pass this flag without the DockSpace() api. Testing this because we
20674 // also setup the hole in host_window->ParentNode
20675 ImGuiDockNode *root_node = DockNodeGetRootNode(central_node);
20676 ImRect root_rect(root_node->Pos, root_node->Pos + root_node->Size);
20677 ImRect hole_rect(central_node->Pos, central_node->Pos + central_node->Size);
20678 if (hole_rect.Min.x > root_rect.Min.x)
20679 {
20680 hole_rect.Min.x += g.WindowsBorderHoverPadding;
20681 }
20682 if (hole_rect.Max.x < root_rect.Max.x)
20683 {
20684 hole_rect.Max.x -= g.WindowsBorderHoverPadding;
20685 }
20686 if (hole_rect.Min.y > root_rect.Min.y)
20687 {
20688 hole_rect.Min.y += g.WindowsBorderHoverPadding;
20689 }
20690 if (hole_rect.Max.y < root_rect.Max.y)
20691 {
20692 hole_rect.Max.y -= g.WindowsBorderHoverPadding;
20693 }
20694 // GetForegroundDrawList()->AddRect(hole_rect.Min, hole_rect.Max, IM_COL32(255, 0, 0, 255));
20695 if (central_node_hole && !hole_rect.IsInverted())
20696 {
20697 SetWindowHitTestHole(host_window, hole_rect.Min, hole_rect.Max - hole_rect.Min);
20698 if (host_window->ParentWindow)
20699 SetWindowHitTestHole(host_window->ParentWindow, hole_rect.Min, hole_rect.Max - hole_rect.Min);
20700 }
20701 }
20702
20703 // Update position/size, process and draw resizing splitters
20704 if (node->IsRootNode() && host_window)
20705 {
20706 DockNodeTreeUpdatePosSize(node, host_window->Pos, host_window->Size);
20707 PushStyleColor(ImGuiCol_Separator, g.Style.Colors[ImGuiCol_Border]);
20708 PushStyleColor(ImGuiCol_SeparatorActive, g.Style.Colors[ImGuiCol_ResizeGripActive]);
20709 PushStyleColor(ImGuiCol_SeparatorHovered, g.Style.Colors[ImGuiCol_ResizeGripHovered]);
20710 DockNodeTreeUpdateSplitter(node);
20711 PopStyleColor(3);
20712 }
20713
20714 // Draw empty node background (currently can only be the Central Node)
20715 if (host_window && node->IsEmpty() && node->IsVisible)
20716 {
20717 host_window->DrawList->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_BG);
20718 node->LastBgColor =
20719 (node_flags & ImGuiDockNodeFlags_PassthruCentralNode) ? 0 : GetColorU32(ImGuiCol_DockingEmptyBg);
20720 if (node->LastBgColor != 0)
20721 host_window->DrawList->AddRectFilled(node->Pos, node->Pos + node->Size, node->LastBgColor);
20722 node->IsBgDrawnThisFrame = true;
20723 }
20724
20725 // Draw whole dockspace background if ImGuiDockNodeFlags_PassthruCentralNode if set.
20726 // We need to draw a background at the root level if requested by ImGuiDockNodeFlags_PassthruCentralNode, but we
20727 // will only know the correct pos/size _after_ processing the resizing splitters. So we are using the DrawList
20728 // channel splitting facility to submit drawing primitives out of order!
20729 const bool render_dockspace_bg =
20730 node->IsRootNode() && host_window && (node_flags & ImGuiDockNodeFlags_PassthruCentralNode) != 0;
20731 if (render_dockspace_bg && node->IsVisible)
20732 {
20733 host_window->DrawList->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_BG);
20734 if (central_node_hole)
20735 RenderRectFilledWithHole(host_window->DrawList, node->Rect(), central_node->Rect(),
20736 GetColorU32(ImGuiCol_WindowBg), 0.0f);
20737 else
20738 host_window->DrawList->AddRectFilled(node->Pos, node->Pos + node->Size, GetColorU32(ImGuiCol_WindowBg),
20739 0.0f);
20740 }
20741
20742 // Draw and populate Tab Bar
20743 if (host_window)
20744 host_window->DrawList->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_FG);
20745 if (host_window && node->Windows.Size > 0)
20746 {
20747 DockNodeUpdateTabBar(node, host_window);
20748 }
20749 else
20750 {
20751 node->WantCloseAll = false;
20752 node->WantCloseTabId = 0;
20753 node->IsFocused = false;
20754 }
20755 if (node->TabBar && node->TabBar->SelectedTabId)
20756 node->SelectedTabId = node->TabBar->SelectedTabId;
20757 else if (node->Windows.Size > 0)
20758 node->SelectedTabId = node->Windows[0]->TabId;
20759
20760 // Draw payload drop target
20761 if (host_window && node->IsVisible)
20762 if (node->IsRootNode() && (g.MovingWindow == NULL || g.MovingWindow->RootWindowDockTree != host_window))
20763 BeginDockableDragDropTarget(host_window);
20764
20765 // We update this after DockNodeUpdateTabBar()
20766 node->LastFrameActive = g.FrameCount;
20767
20768 // Recurse into children
20769 // FIXME-DOCK FIXME-OPT: Should not need to recurse into children
20770 if (host_window)
20771 {
20772 if (node->ChildNodes[0])
20773 DockNodeUpdate(node->ChildNodes[0]);
20774 if (node->ChildNodes[1])
20775 DockNodeUpdate(node->ChildNodes[1]);
20776
20777 // Render outer borders last (after the tab bar)
20778 if (node->IsRootNode())
20779 RenderWindowOuterBorders(host_window);
20780 }
20781
20782 // End host window
20783 if (beginned_into_host_window) //-V1020
20784 End();
20785}
20786
20787// Compare TabItem nodes given the last known DockOrder (will persist in .ini file as hint), used to sort tabs when
20788// multiple tabs are added on the same frame.
20789static int IMGUI_CDECL TabItemComparerByDockOrder(const void *lhs, const void *rhs)
20790{
20791 ImGuiWindow *a = ((const ImGuiTabItem *)lhs)->Window;
20792 ImGuiWindow *b = ((const ImGuiTabItem *)rhs)->Window;
20793 if (int d = ((a->DockOrder == -1) ? INT_MAX : a->DockOrder) - ((b->DockOrder == -1) ? INT_MAX : b->DockOrder))
20794 return d;
20795 return (a->BeginOrderWithinContext - b->BeginOrderWithinContext);
20796}
20797
20798// Default handler for g.DockNodeWindowMenuHandler(): display the list of windows for a given dock-node.
20799// This is exceptionally stored in a function pointer to also user applications to tweak this menu (undocumented)
20800// Custom overrides may want to decorate, group, sort entries.
20801// Please note those are internal structures: if you copy this expect occasional breakage.
20802// (if you don't need to modify the "Tabs.Size == 1" behavior/path it is recommend you call this function in your
20803// handler)
20804void ImGui::DockNodeWindowMenuHandler_Default(ImGuiContext *ctx, ImGuiDockNode *node, ImGuiTabBar *tab_bar)
20805{
20806 IM_UNUSED(ctx);
20807 if (tab_bar->Tabs.Size == 1)
20808 {
20809 // "Hide tab bar" option. Being one of our rare user-facing string we pull it from a table.
20810 if (MenuItem(LocalizeGetMsg(ImGuiLocKey_DockingHideTabBar), NULL, node->IsHiddenTabBar()))
20811 node->WantHiddenTabBarToggle = true;
20812 }
20813 else
20814 {
20815 // Display a selectable list of windows in this docking node
20816 for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
20817 {
20818 ImGuiTabItem *tab = &tab_bar->Tabs[tab_n];
20819 if (tab->Flags & ImGuiTabItemFlags_Button)
20820 continue;
20821 if (Selectable(TabBarGetTabName(tab_bar, tab), tab->ID == tab_bar->SelectedTabId))
20822 TabBarQueueFocus(tab_bar, tab);
20823 SameLine();
20824 Text(" ");
20825 }
20826 }
20827}
20828
20829static void ImGui::DockNodeWindowMenuUpdate(ImGuiDockNode *node, ImGuiTabBar *tab_bar)
20830{
20831 // Try to position the menu so it is more likely to stays within the same viewport
20832 ImGuiContext &g = *GImGui;
20833 if (g.Style.WindowMenuButtonPosition == ImGuiDir_Left)
20834 SetNextWindowPos(ImVec2(node->Pos.x, node->Pos.y + GetFrameHeight()), ImGuiCond_Always, ImVec2(0.0f, 0.0f));
20835 else
20836 SetNextWindowPos(ImVec2(node->Pos.x + node->Size.x, node->Pos.y + GetFrameHeight()), ImGuiCond_Always,
20837 ImVec2(1.0f, 0.0f));
20838 if (BeginPopup("#WindowMenu"))
20839 {
20840 node->IsFocused = true;
20841 g.DockNodeWindowMenuHandler(&g, node, tab_bar);
20842 EndPopup();
20843 }
20844}
20845
20846// User helper to append/amend into a dock node tab bar. Most commonly used to add e.g. a "+" button.
20847bool ImGui::DockNodeBeginAmendTabBar(ImGuiDockNode *node)
20848{
20849 if (node->TabBar == NULL || node->HostWindow == NULL)
20850 return false;
20851 if (node->MergedFlags & ImGuiDockNodeFlags_KeepAliveOnly)
20852 return false;
20853 if (node->TabBar->ID == 0)
20854 return false;
20855 Begin(node->HostWindow->Name);
20856 PushOverrideID(node->ID);
20857 bool ret = BeginTabBarEx(node->TabBar, node->TabBar->BarRect, node->TabBar->Flags);
20858 IM_UNUSED(ret);
20859 IM_ASSERT(ret);
20860 return true;
20861}
20862
20863void ImGui::DockNodeEndAmendTabBar()
20864{
20865 EndTabBar();
20866 PopID();
20867 End();
20868}
20869
20870static bool IsDockNodeTitleBarHighlighted(ImGuiDockNode *node, ImGuiDockNode *root_node)
20871{
20872 // CTRL+Tab highlight (only highlighting leaf node, not whole hierarchy)
20873 ImGuiContext &g = *GImGui;
20874 if (g.NavWindowingTarget)
20875 return (g.NavWindowingTarget->DockNode == node);
20876
20877 // FIXME-DOCKING: May want alternative to treat central node void differently? e.g. if (g.NavWindow == host_window)
20878 if (g.NavWindow && root_node->LastFocusedNodeId == node->ID)
20879 {
20880 // FIXME: This could all be backed in RootWindowForTitleBarHighlight? Probably need to reorganize for both dock
20881 // nodes + other RootWindowForTitleBarHighlight users (not-node)
20882 ImGuiWindow *parent_window = g.NavWindow->RootWindow;
20883 while (parent_window->Flags & ImGuiWindowFlags_ChildMenu)
20884 parent_window = parent_window->ParentWindow->RootWindow;
20885 ImGuiDockNode *start_parent_node =
20886 parent_window->DockNodeAsHost ? parent_window->DockNodeAsHost : parent_window->DockNode;
20887 for (ImGuiDockNode *parent_node = start_parent_node; parent_node != NULL;
20888 parent_node = parent_node->HostWindow ? parent_node->HostWindow->RootWindow->DockNode : NULL)
20889 if ((parent_node = ImGui::DockNodeGetRootNode(parent_node)) == root_node)
20890 return true;
20891 }
20892 return false;
20893}
20894
20895// Submit the tab bar corresponding to a dock node and various housekeeping details.
20896static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode *node, ImGuiWindow *host_window)
20897{
20898 ImGuiContext &g = *GImGui;
20899 ImGuiStyle &style = g.Style;
20900
20901 const bool node_was_active = (node->LastFrameActive + 1 == g.FrameCount);
20902 const bool closed_all = node->WantCloseAll && node_was_active;
20903 const ImGuiID closed_one = node->WantCloseTabId && node_was_active;
20904 node->WantCloseAll = false;
20905 node->WantCloseTabId = 0;
20906
20907 // Decide if we should use a focused title bar color
20908 bool is_focused = false;
20909 ImGuiDockNode *root_node = DockNodeGetRootNode(node);
20910 if (IsDockNodeTitleBarHighlighted(node, root_node))
20911 is_focused = true;
20912
20913 // Hidden tab bar will show a triangle on the upper-left (in Begin)
20914 if (node->IsHiddenTabBar() || node->IsNoTabBar())
20915 {
20916 node->VisibleWindow = (node->Windows.Size > 0) ? node->Windows[0] : NULL;
20917 node->IsFocused = is_focused;
20918 if (is_focused)
20919 node->LastFrameFocused = g.FrameCount;
20920 if (node->VisibleWindow)
20921 {
20922 // Notify root of visible window (used to display title in OS task bar)
20923 if (is_focused || root_node->VisibleWindow == NULL)
20924 root_node->VisibleWindow = node->VisibleWindow;
20925 if (node->TabBar)
20926 node->TabBar->VisibleTabId = node->VisibleWindow->TabId;
20927 }
20928 return;
20929 }
20930
20931 // Move ourselves to the Menu layer (so we can be accessed by tapping Alt) + undo SkipItems flag in order to draw
20932 // over the title bar even if the window is collapsed
20933 bool backup_skip_item = host_window->SkipItems;
20934 if (!node->IsDockSpace())
20935 {
20936 host_window->SkipItems = false;
20937 host_window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;
20938 }
20939
20940 // Use PushOverrideID() instead of PushID() to use the node id _without_ the host window ID.
20941 // This is to facilitate computing those ID from the outside, and will affect more or less only the ID of the
20942 // collapse button, popup and tabs, as docked windows themselves will override the stack with their own root ID.
20943 PushOverrideID(node->ID);
20944 ImGuiTabBar *tab_bar = node->TabBar;
20945 bool tab_bar_is_recreated = (tab_bar == NULL); // Tab bar are automatically destroyed when a node gets hidden
20946 if (tab_bar == NULL)
20947 {
20948 DockNodeAddTabBar(node);
20949 tab_bar = node->TabBar;
20950 }
20951
20952 ImGuiID focus_tab_id = 0;
20953 node->IsFocused = is_focused;
20954
20955 const ImGuiDockNodeFlags node_flags = node->MergedFlags;
20956 const bool has_window_menu_button =
20957 (node_flags & ImGuiDockNodeFlags_NoWindowMenuButton) == 0 && (style.WindowMenuButtonPosition != ImGuiDir_None);
20958
20959 // In a dock node, the Collapse Button turns into the Window Menu button.
20960 // FIXME-DOCK FIXME-OPT: Could we recycle popups id across multiple dock nodes?
20961 if (has_window_menu_button && IsPopupOpen("#WindowMenu"))
20962 {
20963 ImGuiID next_selected_tab_id = tab_bar->NextSelectedTabId;
20964 DockNodeWindowMenuUpdate(node, tab_bar);
20965 if (tab_bar->NextSelectedTabId != 0 && tab_bar->NextSelectedTabId != next_selected_tab_id)
20966 focus_tab_id = tab_bar->NextSelectedTabId;
20967 is_focused |= node->IsFocused;
20968 }
20969
20970 // Layout
20971 ImRect title_bar_rect, tab_bar_rect;
20972 ImVec2 window_menu_button_pos;
20973 ImVec2 close_button_pos;
20974 DockNodeCalcTabBarLayout(node, &title_bar_rect, &tab_bar_rect, &window_menu_button_pos, &close_button_pos);
20975
20976 // Submit new tabs, they will be added as Unsorted and sorted below based on relative DockOrder value.
20977 const int tabs_count_old = tab_bar->Tabs.Size;
20978 for (int window_n = 0; window_n < node->Windows.Size; window_n++)
20979 {
20980 ImGuiWindow *window = node->Windows[window_n];
20981 if (TabBarFindTabByID(tab_bar, window->TabId) == NULL)
20982 TabBarAddTab(tab_bar, ImGuiTabItemFlags_Unsorted, window);
20983 }
20984
20985 // Title bar
20986 if (is_focused)
20987 node->LastFrameFocused = g.FrameCount;
20988 ImU32 title_bar_col = GetColorU32(host_window->Collapsed ? ImGuiCol_TitleBgCollapsed
20989 : is_focused ? ImGuiCol_TitleBgActive
20990 : ImGuiCol_TitleBg);
20991 ImDrawFlags rounding_flags =
20992 CalcRoundingFlagsForRectInRect(title_bar_rect, host_window->Rect(), g.Style.DockingSeparatorSize);
20993 host_window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col,
20994 host_window->WindowRounding, rounding_flags);
20995
20996 // Docking/Collapse button
20997 if (has_window_menu_button)
20998 {
20999 if (CollapseButton(host_window->GetID("#COLLAPSE"), window_menu_button_pos,
21000 node)) // == DockNodeGetWindowMenuButtonId(node)
21001 OpenPopup("#WindowMenu");
21002 if (IsItemActive())
21003 focus_tab_id = tab_bar->SelectedTabId;
21004 if (IsItemHovered(ImGuiHoveredFlags_ForTooltip | ImGuiHoveredFlags_DelayNormal) && g.HoveredIdTimer > 0.5f)
21005 SetTooltip("%s", LocalizeGetMsg(ImGuiLocKey_DockingDragToUndockOrMoveNode));
21006 }
21007
21008 // If multiple tabs are appearing on the same frame, sort them based on their persistent DockOrder value
21009 int tabs_unsorted_start = tab_bar->Tabs.Size;
21010 for (int tab_n = tab_bar->Tabs.Size - 1; tab_n >= 0 && (tab_bar->Tabs[tab_n].Flags & ImGuiTabItemFlags_Unsorted);
21011 tab_n--)
21012 {
21013 // FIXME-DOCK: Consider only clearing the flag after the tab has been alive for a few consecutive frames,
21014 // allowing late comers to not break sorting?
21015 tab_bar->Tabs[tab_n].Flags &= ~ImGuiTabItemFlags_Unsorted;
21016 tabs_unsorted_start = tab_n;
21017 }
21018 if (tab_bar->Tabs.Size > tabs_unsorted_start)
21019 {
21020 IMGUI_DEBUG_LOG_DOCKING("[docking] In node 0x%08X: %d new appearing tabs:%s\n", node->ID,
21021 tab_bar->Tabs.Size - tabs_unsorted_start,
21022 (tab_bar->Tabs.Size > tabs_unsorted_start + 1) ? " (will sort)" : "");
21023 for (int tab_n = tabs_unsorted_start; tab_n < tab_bar->Tabs.Size; tab_n++)
21024 {
21025 ImGuiTabItem *tab = &tab_bar->Tabs[tab_n];
21026 IM_UNUSED(tab);
21027 IMGUI_DEBUG_LOG_DOCKING("[docking] - Tab 0x%08X '%s' Order %d\n", tab->ID, TabBarGetTabName(tab_bar, tab),
21028 tab->Window ? tab->Window->DockOrder : -1);
21029 }
21030 IMGUI_DEBUG_LOG_DOCKING("[docking] SelectedTabId = 0x%08X, NavWindow->TabId = 0x%08X\n", node->SelectedTabId,
21031 g.NavWindow ? g.NavWindow->TabId : -1);
21032 if (tab_bar->Tabs.Size > tabs_unsorted_start + 1)
21033 ImQsort(tab_bar->Tabs.Data + tabs_unsorted_start, tab_bar->Tabs.Size - tabs_unsorted_start,
21034 sizeof(ImGuiTabItem), TabItemComparerByDockOrder);
21035 }
21036
21037 // Apply NavWindow focus back to the tab bar
21038 if (g.NavWindow && g.NavWindow->RootWindow->DockNode == node)
21039 tab_bar->SelectedTabId = g.NavWindow->RootWindow->TabId;
21040
21041 // Selected newly added tabs, or persistent tab ID if the tab bar was just recreated
21042 if (tab_bar_is_recreated && TabBarFindTabByID(tab_bar, node->SelectedTabId) != NULL)
21043 tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = node->SelectedTabId;
21044 else if (tab_bar->Tabs.Size > tabs_count_old)
21045 tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = tab_bar->Tabs.back().Window->TabId;
21046
21047 // Begin tab bar
21048 ImGuiTabBarFlags tab_bar_flags =
21049 ImGuiTabBarFlags_Reorderable |
21050 ImGuiTabBarFlags_AutoSelectNewTabs; // | ImGuiTabBarFlags_NoTabListScrollingButtons);
21051 tab_bar_flags |=
21052 ImGuiTabBarFlags_SaveSettings | ImGuiTabBarFlags_DockNode; // | ImGuiTabBarFlags_FittingPolicyScroll;
21053 tab_bar_flags |= ImGuiTabBarFlags_DrawSelectedOverline;
21054 if (!host_window->Collapsed && is_focused)
21055 tab_bar_flags |= ImGuiTabBarFlags_IsFocused;
21056 tab_bar->ID = GetID("#TabBar");
21057 tab_bar->SeparatorMinX = node->Pos.x + host_window->WindowBorderSize; // Separator cover the whole node width
21058 tab_bar->SeparatorMaxX = node->Pos.x + node->Size.x - host_window->WindowBorderSize;
21059 BeginTabBarEx(tab_bar, tab_bar_rect, tab_bar_flags);
21060 // host_window->DrawList->AddRect(tab_bar_rect.Min, tab_bar_rect.Max, IM_COL32(255,0,255,255));
21061
21062 // Backup style colors
21063 ImVec4 backup_style_cols[ImGuiWindowDockStyleCol_COUNT];
21064 for (int color_n = 0; color_n < ImGuiWindowDockStyleCol_COUNT; color_n++)
21065 backup_style_cols[color_n] = g.Style.Colors[GWindowDockStyleColors[color_n]];
21066
21067 // Submit actual tabs
21068 node->VisibleWindow = NULL;
21069 for (int window_n = 0; window_n < node->Windows.Size; window_n++)
21070 {
21071 ImGuiWindow *window = node->Windows[window_n];
21072 if ((closed_all || closed_one == window->TabId) && window->HasCloseButton &&
21073 !(window->Flags & ImGuiWindowFlags_UnsavedDocument))
21074 continue;
21075 if (window->LastFrameActive + 1 >= g.FrameCount || !node_was_active)
21076 {
21077 ImGuiTabItemFlags tab_item_flags = 0;
21078 tab_item_flags |= window->WindowClass.TabItemFlagsOverrideSet;
21079 if (window->Flags & ImGuiWindowFlags_UnsavedDocument)
21080 tab_item_flags |= ImGuiTabItemFlags_UnsavedDocument;
21081 if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)
21082 tab_item_flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton;
21083
21084 // Apply stored style overrides for the window
21085 for (int color_n = 0; color_n < ImGuiWindowDockStyleCol_COUNT; color_n++)
21086 g.Style.Colors[GWindowDockStyleColors[color_n]] =
21087 ColorConvertU32ToFloat4(window->DockStyle.Colors[color_n]);
21088
21089 // Note that TabItemEx() calls TabBarCalcTabID() so our tab item ID will ignore the current ID stack
21090 // (rightly so)
21091 bool tab_open = true;
21092 TabItemEx(tab_bar, window->Name, window->HasCloseButton ? &tab_open : NULL, tab_item_flags, window);
21093 if (!tab_open)
21094 node->WantCloseTabId = window->TabId;
21095 if (tab_bar->VisibleTabId == window->TabId)
21096 node->VisibleWindow = window;
21097
21098 // Store last item data so it can be queried with IsItemXXX functions after the user Begin() call
21099 window->DC.DockTabItemStatusFlags = g.LastItemData.StatusFlags;
21100 window->DC.DockTabItemRect = g.LastItemData.Rect;
21101
21102 // Update navigation ID on menu layer
21103 if (g.NavWindow && g.NavWindow->RootWindow == window &&
21104 (window->DC.NavLayersActiveMask & (1 << ImGuiNavLayer_Menu)) == 0)
21105 host_window->NavLastIds[1] = window->TabId;
21106 }
21107 }
21108
21109 // Restore style colors
21110 for (int color_n = 0; color_n < ImGuiWindowDockStyleCol_COUNT; color_n++)
21111 g.Style.Colors[GWindowDockStyleColors[color_n]] = backup_style_cols[color_n];
21112
21113 // Notify root of visible window (used to display title in OS task bar)
21114 if (node->VisibleWindow)
21115 if (is_focused || root_node->VisibleWindow == NULL)
21116 root_node->VisibleWindow = node->VisibleWindow;
21117
21118 // Close button (after VisibleWindow was updated)
21119 // Note that VisibleWindow may have been overrided by CTRL+Tabbing, so VisibleWindow->TabId may be != from
21120 // tab_bar->SelectedTabId
21121 const bool close_button_is_enabled =
21122 node->HasCloseButton && node->VisibleWindow && node->VisibleWindow->HasCloseButton;
21123 const bool close_button_is_visible = node->HasCloseButton;
21124 // const bool close_button_is_visible = close_button_is_enabled; // Most people would expect this behavior of not
21125 // even showing the button (leaving a hole since we can't claim that space as other windows in the tba bar have one)
21126 if (close_button_is_visible)
21127 {
21128 if (!close_button_is_enabled)
21129 {
21130 PushItemFlag(ImGuiItemFlags_Disabled, true);
21131 PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_Text] * ImVec4(1.0f, 1.0f, 1.0f, 0.4f));
21132 }
21133 if (CloseButton(host_window->GetID("#CLOSE"), close_button_pos))
21134 {
21135 node->WantCloseAll = true;
21136 for (int n = 0; n < tab_bar->Tabs.Size; n++)
21137 TabBarCloseTab(tab_bar, &tab_bar->Tabs[n]);
21138 }
21139 // if (IsItemActive())
21140 // focus_tab_id = tab_bar->SelectedTabId;
21141 if (!close_button_is_enabled)
21142 {
21143 PopStyleColor();
21144 PopItemFlag();
21145 }
21146 }
21147
21148 // When clicking on the title bar outside of tabs, we still focus the selected tab for that node
21149 // FIXME: TabItems submitted earlier use AllowItemOverlap so we manually perform a more specific test for now
21150 // (hovered || held) in order to not cover them.
21151 ImGuiID title_bar_id = host_window->GetID("#TITLEBAR");
21152 if (g.HoveredId == 0 || g.HoveredId == title_bar_id || g.ActiveId == title_bar_id)
21153 {
21154 // AllowOverlap mode required for appending into dock node tab bar,
21155 // otherwise dragging window will steal HoveredId and amended tabs cannot get them.
21156 bool held;
21157 KeepAliveID(title_bar_id);
21158 ButtonBehavior(title_bar_rect, title_bar_id, NULL, &held, ImGuiButtonFlags_AllowOverlap);
21159 if (g.HoveredId == title_bar_id)
21160 {
21161 g.LastItemData.ID = title_bar_id;
21162 }
21163 if (held)
21164 {
21165 if (IsMouseClicked(0))
21166 focus_tab_id = tab_bar->SelectedTabId;
21167
21168 // Forward moving request to selected window
21169 if (ImGuiTabItem *tab = TabBarFindTabByID(tab_bar, tab_bar->SelectedTabId))
21170 StartMouseMovingWindowOrNode(tab->Window ? tab->Window : node->HostWindow, node,
21171 false); // Undock from tab bar empty space
21172 }
21173 }
21174
21175 // Forward focus from host node to selected window
21176 // if (is_focused && g.NavWindow == host_window && !g.NavWindowingTarget)
21177 // focus_tab_id = tab_bar->SelectedTabId;
21178
21179 // When clicked on a tab we requested focus to the docked child
21180 // This overrides the value set by "forward focus from host node to selected window".
21181 if (tab_bar->NextSelectedTabId)
21182 focus_tab_id = tab_bar->NextSelectedTabId;
21183
21184 // Apply navigation focus
21185 if (focus_tab_id != 0)
21186 if (ImGuiTabItem *tab = TabBarFindTabByID(tab_bar, focus_tab_id))
21187 if (tab->Window)
21188 {
21189 FocusWindow(tab->Window);
21190 NavInitWindow(tab->Window, false);
21191 }
21192
21193 EndTabBar();
21194 PopID();
21195
21196 // Restore SkipItems flag
21197 if (!node->IsDockSpace())
21198 {
21199 host_window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
21200 host_window->SkipItems = backup_skip_item;
21201 }
21202}
21203
21204static void ImGui::DockNodeAddTabBar(ImGuiDockNode *node)
21205{
21206 IM_ASSERT(node->TabBar == NULL);
21207 node->TabBar = IM_NEW(ImGuiTabBar);
21208}
21209
21210static void ImGui::DockNodeRemoveTabBar(ImGuiDockNode *node)
21211{
21212 if (node->TabBar == NULL)
21213 return;
21214 IM_DELETE(node->TabBar);
21215 node->TabBar = NULL;
21216}
21217
21218static bool DockNodeIsDropAllowedOne(ImGuiWindow *payload, ImGuiWindow *host_window)
21219{
21220 if (host_window->DockNodeAsHost && host_window->DockNodeAsHost->IsDockSpace() &&
21221 payload->BeginOrderWithinContext < host_window->BeginOrderWithinContext)
21222 return false;
21223
21224 ImGuiWindowClass *host_class =
21225 host_window->DockNodeAsHost ? &host_window->DockNodeAsHost->WindowClass : &host_window->WindowClass;
21226 ImGuiWindowClass *payload_class = &payload->WindowClass;
21227 if (host_class->ClassId != payload_class->ClassId)
21228 {
21229 bool pass = false;
21230 if (host_class->ClassId != 0 && host_class->DockingAllowUnclassed && payload_class->ClassId == 0)
21231 pass = true;
21232 if (payload_class->ClassId != 0 && payload_class->DockingAllowUnclassed && host_class->ClassId == 0)
21233 pass = true;
21234 if (!pass)
21235 return false;
21236 }
21237
21238 // Prevent docking any window created above a popup
21239 // Technically we should support it (e.g. in the case of a long-lived modal window that had fancy docking features),
21240 // by e.g. adding a 'if (!ImGui::IsWindowWithinBeginStackOf(host_window, popup_window))' test.
21241 // But it would requires more work on our end because the dock host windows is technically created in NewFrame()
21242 // and our ->ParentXXX and ->RootXXX pointers inside windows are currently mislading or lacking.
21243 ImGuiContext &g = *GImGui;
21244 for (int i = g.OpenPopupStack.Size - 1; i >= 0; i--)
21245 if (ImGuiWindow *popup_window = g.OpenPopupStack[i].Window)
21246 if (ImGui::IsWindowWithinBeginStackOf(payload,
21247 popup_window)) // Payload is created from within a popup begin stack.
21248 return false;
21249
21250 return true;
21251}
21252
21253static bool ImGui::DockNodeIsDropAllowed(ImGuiWindow *host_window, ImGuiWindow *root_payload)
21254{
21255 if (root_payload->DockNodeAsHost && root_payload->DockNodeAsHost->IsSplitNode()) // FIXME-DOCK: Missing filtering
21256 return true;
21257
21258 const int payload_count = root_payload->DockNodeAsHost ? root_payload->DockNodeAsHost->Windows.Size : 1;
21259 for (int payload_n = 0; payload_n < payload_count; payload_n++)
21260 {
21261 ImGuiWindow *payload =
21262 root_payload->DockNodeAsHost ? root_payload->DockNodeAsHost->Windows[payload_n] : root_payload;
21263 if (DockNodeIsDropAllowedOne(payload, host_window))
21264 return true;
21265 }
21266 return false;
21267}
21268
21269// window menu button == collapse button when not in a dock node.
21270// FIXME: This is similar to RenderWindowTitleBarContents(), may want to share code.
21271static void ImGui::DockNodeCalcTabBarLayout(const ImGuiDockNode *node, ImRect *out_title_rect, ImRect *out_tab_bar_rect,
21272 ImVec2 *out_window_menu_button_pos, ImVec2 *out_close_button_pos)
21273{
21274 ImGuiContext &g = *GImGui;
21275 ImGuiStyle &style = g.Style;
21276
21277 ImRect r = ImRect(node->Pos.x, node->Pos.y, node->Pos.x + node->Size.x,
21278 node->Pos.y + g.FontSize + g.Style.FramePadding.y * 2.0f);
21279 if (out_title_rect)
21280 {
21281 *out_title_rect = r;
21282 }
21283
21284 r.Min.x += style.WindowBorderSize;
21285 r.Max.x -= style.WindowBorderSize;
21286
21287 float button_sz = g.FontSize;
21288 r.Min.x += style.FramePadding.x;
21289 r.Max.x -= style.FramePadding.x;
21290 ImVec2 window_menu_button_pos = ImVec2(r.Min.x, r.Min.y + style.FramePadding.y);
21291 if (node->HasCloseButton)
21292 {
21293 if (out_close_button_pos)
21294 *out_close_button_pos = ImVec2(r.Max.x - button_sz, r.Min.y + style.FramePadding.y);
21295 r.Max.x -= button_sz + style.ItemInnerSpacing.x;
21296 }
21297 if (node->HasWindowMenuButton && style.WindowMenuButtonPosition == ImGuiDir_Left)
21298 {
21299 r.Min.x += button_sz + style.ItemInnerSpacing.x;
21300 }
21301 else if (node->HasWindowMenuButton && style.WindowMenuButtonPosition == ImGuiDir_Right)
21302 {
21303 window_menu_button_pos = ImVec2(r.Max.x - button_sz, r.Min.y + style.FramePadding.y);
21304 r.Max.x -= button_sz + style.ItemInnerSpacing.x;
21305 }
21306 if (out_tab_bar_rect)
21307 {
21308 *out_tab_bar_rect = r;
21309 }
21310 if (out_window_menu_button_pos)
21311 {
21312 *out_window_menu_button_pos = window_menu_button_pos;
21313 }
21314}
21315
21316void ImGui::DockNodeCalcSplitRects(ImVec2 &pos_old, ImVec2 &size_old, ImVec2 &pos_new, ImVec2 &size_new, ImGuiDir dir,
21317 ImVec2 size_new_desired)
21318{
21319 ImGuiContext &g = *GImGui;
21320 const float dock_spacing = g.Style.ItemInnerSpacing.x;
21321 const ImGuiAxis axis = (dir == ImGuiDir_Left || dir == ImGuiDir_Right) ? ImGuiAxis_X : ImGuiAxis_Y;
21322 pos_new[axis ^ 1] = pos_old[axis ^ 1];
21323 size_new[axis ^ 1] = size_old[axis ^ 1];
21324
21325 // Distribute size on given axis (with a desired size or equally)
21326 const float w_avail = size_old[axis] - dock_spacing;
21327 if (size_new_desired[axis] > 0.0f && size_new_desired[axis] <= w_avail * 0.5f)
21328 {
21329 size_new[axis] = size_new_desired[axis];
21330 size_old[axis] = IM_TRUNC(w_avail - size_new[axis]);
21331 }
21332 else
21333 {
21334 size_new[axis] = IM_TRUNC(w_avail * 0.5f);
21335 size_old[axis] = IM_TRUNC(w_avail - size_new[axis]);
21336 }
21337
21338 // Position each node
21339 if (dir == ImGuiDir_Right || dir == ImGuiDir_Down)
21340 {
21341 pos_new[axis] = pos_old[axis] + size_old[axis] + dock_spacing;
21342 }
21343 else if (dir == ImGuiDir_Left || dir == ImGuiDir_Up)
21344 {
21345 pos_new[axis] = pos_old[axis];
21346 pos_old[axis] = pos_new[axis] + size_new[axis] + dock_spacing;
21347 }
21348}
21349
21350// Retrieve the drop rectangles for a given direction or for the center + perform hit testing.
21351bool ImGui::DockNodeCalcDropRectsAndTestMousePos(const ImRect &parent, ImGuiDir dir, ImRect &out_r, bool outer_docking,
21352 ImVec2 *test_mouse_pos)
21353{
21354 ImGuiContext &g = *GImGui;
21355
21356 const float parent_smaller_axis = ImMin(parent.GetWidth(), parent.GetHeight());
21357 const float hs_for_central_nodes = ImMin(g.FontSize * 1.5f, ImMax(g.FontSize * 0.5f, parent_smaller_axis / 8.0f));
21358 float hs_w; // Half-size, longer axis
21359 float hs_h; // Half-size, smaller axis
21360 ImVec2 off; // Distance from edge or center
21361 if (outer_docking)
21362 {
21363 // hs_w = ImTrunc(ImClamp(parent_smaller_axis - hs_for_central_nodes * 4.0f, g.FontSize * 0.5f, g.FontSize
21364 // * 8.0f)); hs_h = ImTrunc(hs_w * 0.15f); off = ImVec2(ImTrunc(parent.GetWidth() * 0.5f -
21365 // GetFrameHeightWithSpacing() * 1.4f - hs_h), ImTrunc(parent.GetHeight() * 0.5f - GetFrameHeightWithSpacing()
21366 // * 1.4f - hs_h));
21367 hs_w = ImTrunc(hs_for_central_nodes * 1.50f);
21368 hs_h = ImTrunc(hs_for_central_nodes * 0.80f);
21369 off = ImTrunc(ImVec2(parent.GetWidth() * 0.5f - hs_h, parent.GetHeight() * 0.5f - hs_h));
21370 }
21371 else
21372 {
21373 hs_w = ImTrunc(hs_for_central_nodes);
21374 hs_h = ImTrunc(hs_for_central_nodes * 0.90f);
21375 off = ImTrunc(ImVec2(hs_w * 2.40f, hs_w * 2.40f));
21376 }
21377
21378 ImVec2 c = ImTrunc(parent.GetCenter());
21379 if (dir == ImGuiDir_None)
21380 {
21381 out_r = ImRect(c.x - hs_w, c.y - hs_w, c.x + hs_w, c.y + hs_w);
21382 }
21383 else if (dir == ImGuiDir_Up)
21384 {
21385 out_r = ImRect(c.x - hs_w, c.y - off.y - hs_h, c.x + hs_w, c.y - off.y + hs_h);
21386 }
21387 else if (dir == ImGuiDir_Down)
21388 {
21389 out_r = ImRect(c.x - hs_w, c.y + off.y - hs_h, c.x + hs_w, c.y + off.y + hs_h);
21390 }
21391 else if (dir == ImGuiDir_Left)
21392 {
21393 out_r = ImRect(c.x - off.x - hs_h, c.y - hs_w, c.x - off.x + hs_h, c.y + hs_w);
21394 }
21395 else if (dir == ImGuiDir_Right)
21396 {
21397 out_r = ImRect(c.x + off.x - hs_h, c.y - hs_w, c.x + off.x + hs_h, c.y + hs_w);
21398 }
21399
21400 if (test_mouse_pos == NULL)
21401 return false;
21402
21403 ImRect hit_r = out_r;
21404 if (!outer_docking)
21405 {
21406 // Custom hit testing for the 5-way selection, designed to reduce flickering when moving diagonally between
21407 // sides
21408 hit_r.Expand(ImTrunc(hs_w * 0.30f));
21409 ImVec2 mouse_delta = (*test_mouse_pos - c);
21410 float mouse_delta_len2 = ImLengthSqr(mouse_delta);
21411 float r_threshold_center = hs_w * 1.4f;
21412 float r_threshold_sides = hs_w * (1.4f + 1.2f);
21413 if (mouse_delta_len2 < r_threshold_center * r_threshold_center)
21414 return (dir == ImGuiDir_None);
21415 if (mouse_delta_len2 < r_threshold_sides * r_threshold_sides)
21416 return (dir == ImGetDirQuadrantFromDelta(mouse_delta.x, mouse_delta.y));
21417 }
21418 return hit_r.Contains(*test_mouse_pos);
21419}
21420
21421// host_node may be NULL if the window doesn't have a DockNode already.
21422// FIXME-DOCK: This is misnamed since it's also doing the filtering.
21423static void ImGui::DockNodePreviewDockSetup(ImGuiWindow *host_window, ImGuiDockNode *host_node,
21424 ImGuiWindow *payload_window, ImGuiDockNode *payload_node,
21425 ImGuiDockPreviewData *data, bool is_explicit_target, bool is_outer_docking)
21426{
21427 ImGuiContext &g = *GImGui;
21428
21429 // There is an edge case when docking into a dockspace which only has inactive nodes.
21430 // In this case DockNodeTreeFindNodeByPos() will have selected a leaf node which is inactive.
21431 // Because the inactive leaf node doesn't have proper pos/size yet, we'll use the root node as reference.
21432 if (payload_node == NULL)
21433 payload_node = payload_window->DockNodeAsHost;
21434 ImGuiDockNode *ref_node_for_rect =
21435 (host_node && !host_node->IsVisible) ? DockNodeGetRootNode(host_node) : host_node;
21436 if (ref_node_for_rect)
21437 IM_ASSERT(ref_node_for_rect->IsVisible == true);
21438
21439 // Filter, figure out where we are allowed to dock
21440 ImGuiDockNodeFlags src_node_flags =
21441 payload_node ? payload_node->MergedFlags : payload_window->WindowClass.DockNodeFlagsOverrideSet;
21442 ImGuiDockNodeFlags dst_node_flags =
21443 host_node ? host_node->MergedFlags : host_window->WindowClass.DockNodeFlagsOverrideSet;
21444 data->IsCenterAvailable = true;
21445 if (is_outer_docking)
21446 data->IsCenterAvailable = false;
21447 else if (dst_node_flags & ImGuiDockNodeFlags_NoDockingOverMe)
21448 data->IsCenterAvailable = false;
21449 else if (host_node && (dst_node_flags & ImGuiDockNodeFlags_NoDockingOverCentralNode) && host_node->IsCentralNode())
21450 data->IsCenterAvailable = false;
21451 else if ((!host_node || !host_node->IsEmpty()) && payload_node && payload_node->IsSplitNode() &&
21452 (payload_node->OnlyNodeWithWindows == NULL)) // Is _visibly_ split?
21453 data->IsCenterAvailable = false;
21454 else if ((src_node_flags & ImGuiDockNodeFlags_NoDockingOverOther) && (!host_node || !host_node->IsEmpty()))
21455 data->IsCenterAvailable = false;
21456 else if ((src_node_flags & ImGuiDockNodeFlags_NoDockingOverEmpty) && host_node && host_node->IsEmpty())
21457 data->IsCenterAvailable = false;
21458
21459 data->IsSidesAvailable = true;
21460 if ((dst_node_flags & ImGuiDockNodeFlags_NoDockingSplit) || g.IO.ConfigDockingNoSplit)
21461 data->IsSidesAvailable = false;
21462 else if (!is_outer_docking && host_node && host_node->ParentNode == NULL && host_node->IsCentralNode())
21463 data->IsSidesAvailable = false;
21464 else if (src_node_flags & ImGuiDockNodeFlags_NoDockingSplitOther)
21465 data->IsSidesAvailable = false;
21466
21467 // Build a tentative future node (reuse same structure because it is practical. Shape will be readjusted when
21468 // previewing a split)
21469 data->FutureNode.HasCloseButton =
21470 (host_node ? host_node->HasCloseButton : host_window->HasCloseButton) || (payload_window->HasCloseButton);
21471 data->FutureNode.HasWindowMenuButton = host_node ? true : ((host_window->Flags & ImGuiWindowFlags_NoCollapse) == 0);
21472 data->FutureNode.Pos = ref_node_for_rect ? ref_node_for_rect->Pos : host_window->Pos;
21473 data->FutureNode.Size = ref_node_for_rect ? ref_node_for_rect->Size : host_window->Size;
21474
21475 // Calculate drop shapes geometry for allowed splitting directions
21476 IM_ASSERT(ImGuiDir_None == -1);
21477 data->SplitNode = host_node;
21478 data->SplitDir = ImGuiDir_None;
21479 data->IsSplitDirExplicit = false;
21480 if (!host_window->Collapsed)
21481 for (int dir = ImGuiDir_None; dir < ImGuiDir_COUNT; dir++)
21482 {
21483 if (dir == ImGuiDir_None && !data->IsCenterAvailable)
21484 continue;
21485 if (dir != ImGuiDir_None && !data->IsSidesAvailable)
21486 continue;
21487 if (DockNodeCalcDropRectsAndTestMousePos(data->FutureNode.Rect(), (ImGuiDir)dir,
21488 data->DropRectsDraw[dir + 1], is_outer_docking, &g.IO.MousePos))
21489 {
21490 data->SplitDir = (ImGuiDir)dir;
21491 data->IsSplitDirExplicit = true;
21492 }
21493 }
21494
21495 // When docking without holding Shift, we only allow and preview docking when hovering over a drop rect or over the
21496 // title bar
21497 data->IsDropAllowed = (data->SplitDir != ImGuiDir_None) || (data->IsCenterAvailable);
21498 if (!is_explicit_target && !data->IsSplitDirExplicit && !g.IO.ConfigDockingWithShift)
21499 data->IsDropAllowed = false;
21500
21501 // Calculate split area
21502 data->SplitRatio = 0.0f;
21503 if (data->SplitDir != ImGuiDir_None)
21504 {
21505 ImGuiDir split_dir = data->SplitDir;
21506 ImGuiAxis split_axis = (split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Right) ? ImGuiAxis_X : ImGuiAxis_Y;
21507 ImVec2 pos_new, pos_old = data->FutureNode.Pos;
21508 ImVec2 size_new, size_old = data->FutureNode.Size;
21509 DockNodeCalcSplitRects(pos_old, size_old, pos_new, size_new, split_dir, payload_window->Size);
21510
21511 // Calculate split ratio so we can pass it down the docking request
21512 float split_ratio = ImSaturate(size_new[split_axis] / data->FutureNode.Size[split_axis]);
21513 data->FutureNode.Pos = pos_new;
21514 data->FutureNode.Size = size_new;
21515 data->SplitRatio =
21516 (split_dir == ImGuiDir_Right || split_dir == ImGuiDir_Down) ? (1.0f - split_ratio) : (split_ratio);
21517 }
21518}
21519
21520static void ImGui::DockNodePreviewDockRender(ImGuiWindow *host_window, ImGuiDockNode *host_node,
21521 ImGuiWindow *root_payload, const ImGuiDockPreviewData *data)
21522{
21523 ImGuiContext &g = *GImGui;
21524 IM_ASSERT(g.CurrentWindow == host_window); // Because we rely on font size to calculate tab sizes
21525
21526 // With this option, we only display the preview on the target viewport, and the payload viewport is made
21527 // transparent. To compensate for the single layer obstructed by the payload, we'll increase the alpha of the
21528 // preview nodes.
21529 const bool is_transparent_payload = g.IO.ConfigDockingTransparentPayload;
21530
21531 // In case the two windows involved are on different viewports, we will draw the overlay on each of them.
21532 int overlay_draw_lists_count = 0;
21533 ImDrawList *overlay_draw_lists[2];
21534 overlay_draw_lists[overlay_draw_lists_count++] = GetForegroundDrawList(host_window->Viewport);
21535 if (host_window->Viewport != root_payload->Viewport && !is_transparent_payload)
21536 overlay_draw_lists[overlay_draw_lists_count++] = GetForegroundDrawList(root_payload->Viewport);
21537
21538 // Draw main preview rectangle
21539 const ImU32 overlay_col_main = GetColorU32(ImGuiCol_DockingPreview, is_transparent_payload ? 0.60f : 0.40f);
21540 const ImU32 overlay_col_drop = GetColorU32(ImGuiCol_DockingPreview, is_transparent_payload ? 0.90f : 0.70f);
21541 const ImU32 overlay_col_drop_hovered = GetColorU32(ImGuiCol_DockingPreview, is_transparent_payload ? 1.20f : 1.00f);
21542 const ImU32 overlay_col_lines = GetColorU32(ImGuiCol_NavWindowingHighlight, is_transparent_payload ? 0.80f : 0.60f);
21543
21544 // Display area preview
21545 const bool can_preview_tabs =
21546 (root_payload->DockNodeAsHost == NULL || root_payload->DockNodeAsHost->Windows.Size > 0);
21547 if (data->IsDropAllowed)
21548 {
21549 ImRect overlay_rect = data->FutureNode.Rect();
21550 if (data->SplitDir == ImGuiDir_None && can_preview_tabs)
21551 overlay_rect.Min.y += GetFrameHeight();
21552 if (data->SplitDir != ImGuiDir_None || data->IsCenterAvailable)
21553 for (int overlay_n = 0; overlay_n < overlay_draw_lists_count; overlay_n++)
21554 overlay_draw_lists[overlay_n]->AddRectFilled(
21555 overlay_rect.Min, overlay_rect.Max, overlay_col_main, host_window->WindowRounding,
21556 CalcRoundingFlagsForRectInRect(overlay_rect, host_window->Rect(), g.Style.DockingSeparatorSize));
21557 }
21558
21559 // Display tab shape/label preview unless we are splitting node (it generally makes the situation harder to read)
21560 if (data->IsDropAllowed && can_preview_tabs && data->SplitDir == ImGuiDir_None && data->IsCenterAvailable)
21561 {
21562 // Compute target tab bar geometry so we can locate our preview tabs
21563 ImRect tab_bar_rect;
21564 DockNodeCalcTabBarLayout(&data->FutureNode, NULL, &tab_bar_rect, NULL, NULL);
21565 ImVec2 tab_pos = tab_bar_rect.Min;
21566 if (host_node && host_node->TabBar)
21567 {
21568 if (!host_node->IsHiddenTabBar() && !host_node->IsNoTabBar())
21569 tab_pos.x +=
21570 host_node->TabBar->WidthAllTabs +
21571 g.Style.ItemInnerSpacing.x; // We don't use OffsetNewTab because when using non-persistent-order tab
21572 // bar it is incremented with each Tab submission.
21573 else
21574 tab_pos.x += g.Style.ItemInnerSpacing.x + TabItemCalcSize(host_node->Windows[0]).x;
21575 }
21576 else if (!(host_window->Flags & ImGuiWindowFlags_DockNodeHost))
21577 {
21578 tab_pos.x +=
21579 g.Style.ItemInnerSpacing.x +
21580 TabItemCalcSize(host_window)
21581 .x; // Account for slight offset which will be added when changing from title bar to tab bar
21582 }
21583
21584 // Draw tab shape/label preview (payload may be a loose window or a host window carrying multiple tabbed
21585 // windows)
21586 if (root_payload->DockNodeAsHost)
21587 IM_ASSERT(root_payload->DockNodeAsHost->Windows.Size <= root_payload->DockNodeAsHost->TabBar->Tabs.Size);
21588 ImGuiTabBar *tab_bar_with_payload = root_payload->DockNodeAsHost ? root_payload->DockNodeAsHost->TabBar : NULL;
21589 const int payload_count = tab_bar_with_payload ? tab_bar_with_payload->Tabs.Size : 1;
21590 for (int payload_n = 0; payload_n < payload_count; payload_n++)
21591 {
21592 // DockNode's TabBar may have non-window Tabs manually appended by user
21593 ImGuiWindow *payload_window =
21594 tab_bar_with_payload ? tab_bar_with_payload->Tabs[payload_n].Window : root_payload;
21595 if (tab_bar_with_payload && payload_window == NULL)
21596 continue;
21597 if (!DockNodeIsDropAllowedOne(payload_window, host_window))
21598 continue;
21599
21600 // Calculate the tab bounding box for each payload window
21601 ImVec2 tab_size = TabItemCalcSize(payload_window);
21602 ImRect tab_bb(tab_pos.x, tab_pos.y, tab_pos.x + tab_size.x, tab_pos.y + tab_size.y);
21603 tab_pos.x += tab_size.x + g.Style.ItemInnerSpacing.x;
21604 const ImU32 overlay_col_text = GetColorU32(payload_window->DockStyle.Colors[ImGuiWindowDockStyleCol_Text]);
21605 const ImU32 overlay_col_tabs =
21606 GetColorU32(payload_window->DockStyle.Colors[ImGuiWindowDockStyleCol_TabSelected]);
21607 PushStyleColor(ImGuiCol_Text, overlay_col_text);
21608 for (int overlay_n = 0; overlay_n < overlay_draw_lists_count; overlay_n++)
21609 {
21610 ImGuiTabItemFlags tab_flags =
21611 (payload_window->Flags & ImGuiWindowFlags_UnsavedDocument) ? ImGuiTabItemFlags_UnsavedDocument : 0;
21612 if (!tab_bar_rect.Contains(tab_bb))
21613 overlay_draw_lists[overlay_n]->PushClipRect(tab_bar_rect.Min, tab_bar_rect.Max);
21614 TabItemBackground(overlay_draw_lists[overlay_n], tab_bb, tab_flags, overlay_col_tabs);
21615 TabItemLabelAndCloseButton(overlay_draw_lists[overlay_n], tab_bb, tab_flags, g.Style.FramePadding,
21616 payload_window->Name, 0, 0, false, NULL, NULL);
21617 if (!tab_bar_rect.Contains(tab_bb))
21618 overlay_draw_lists[overlay_n]->PopClipRect();
21619 }
21620 PopStyleColor();
21621 }
21622 }
21623
21624 // Display drop boxes
21625 const float overlay_rounding = ImMax(3.0f, g.Style.FrameRounding);
21626 for (int dir = ImGuiDir_None; dir < ImGuiDir_COUNT; dir++)
21627 {
21628 if (!data->DropRectsDraw[dir + 1].IsInverted())
21629 {
21630 ImRect draw_r = data->DropRectsDraw[dir + 1];
21631 ImRect draw_r_in = draw_r;
21632 draw_r_in.Expand(-2.0f);
21633 ImU32 overlay_col = (data->SplitDir == (ImGuiDir)dir && data->IsSplitDirExplicit) ? overlay_col_drop_hovered
21634 : overlay_col_drop;
21635 for (int overlay_n = 0; overlay_n < overlay_draw_lists_count; overlay_n++)
21636 {
21637 ImVec2 center = ImFloor(draw_r_in.GetCenter());
21638 overlay_draw_lists[overlay_n]->AddRectFilled(draw_r.Min, draw_r.Max, overlay_col, overlay_rounding);
21639 overlay_draw_lists[overlay_n]->AddRect(draw_r_in.Min, draw_r_in.Max, overlay_col_lines,
21640 overlay_rounding);
21641 if (dir == ImGuiDir_Left || dir == ImGuiDir_Right)
21642 overlay_draw_lists[overlay_n]->AddLine(ImVec2(center.x, draw_r_in.Min.y),
21643 ImVec2(center.x, draw_r_in.Max.y), overlay_col_lines);
21644 if (dir == ImGuiDir_Up || dir == ImGuiDir_Down)
21645 overlay_draw_lists[overlay_n]->AddLine(ImVec2(draw_r_in.Min.x, center.y),
21646 ImVec2(draw_r_in.Max.x, center.y), overlay_col_lines);
21647 }
21648 }
21649
21650 // Stop after ImGuiDir_None
21651 if ((host_node && (host_node->MergedFlags & ImGuiDockNodeFlags_NoDockingSplit)) || g.IO.ConfigDockingNoSplit)
21652 return;
21653 }
21654}
21655
21656//-----------------------------------------------------------------------------
21657// Docking: ImGuiDockNode Tree manipulation functions
21658//-----------------------------------------------------------------------------
21659// - DockNodeTreeSplit()
21660// - DockNodeTreeMerge()
21661// - DockNodeTreeUpdatePosSize()
21662// - DockNodeTreeUpdateSplitterFindTouchingNode()
21663// - DockNodeTreeUpdateSplitter()
21664// - DockNodeTreeFindFallbackLeafNode()
21665// - DockNodeTreeFindNodeByPos()
21666//-----------------------------------------------------------------------------
21667
21668void ImGui::DockNodeTreeSplit(ImGuiContext *ctx, ImGuiDockNode *parent_node, ImGuiAxis split_axis,
21669 int split_inheritor_child_idx, float split_ratio, ImGuiDockNode *new_node)
21670{
21671 ImGuiContext &g = *GImGui;
21672 IM_ASSERT(split_axis != ImGuiAxis_None);
21673
21674 ImGuiDockNode *child_0 = (new_node && split_inheritor_child_idx != 0) ? new_node : DockContextAddNode(ctx, 0);
21675 child_0->ParentNode = parent_node;
21676
21677 ImGuiDockNode *child_1 = (new_node && split_inheritor_child_idx != 1) ? new_node : DockContextAddNode(ctx, 0);
21678 child_1->ParentNode = parent_node;
21679
21680 ImGuiDockNode *child_inheritor = (split_inheritor_child_idx == 0) ? child_0 : child_1;
21681 DockNodeMoveChildNodes(child_inheritor, parent_node);
21682 parent_node->ChildNodes[0] = child_0;
21683 parent_node->ChildNodes[1] = child_1;
21684 parent_node->ChildNodes[split_inheritor_child_idx]->VisibleWindow = parent_node->VisibleWindow;
21685 parent_node->SplitAxis = split_axis;
21686 parent_node->VisibleWindow = NULL;
21687 parent_node->AuthorityForPos = parent_node->AuthorityForSize = ImGuiDataAuthority_DockNode;
21688
21689 float size_avail = (parent_node->Size[split_axis] - g.Style.DockingSeparatorSize);
21690 size_avail = ImMax(size_avail, g.Style.WindowMinSize[split_axis] * 2.0f);
21691 IM_ASSERT(size_avail > 0.0f); // If you created a node manually with DockBuilderAddNode(), you need to also call
21692 // DockBuilderSetNodeSize() before splitting.
21693 child_0->SizeRef = child_1->SizeRef = parent_node->Size;
21694 child_0->SizeRef[split_axis] = ImTrunc(size_avail * split_ratio);
21695 child_1->SizeRef[split_axis] = ImTrunc(size_avail - child_0->SizeRef[split_axis]);
21696
21697 DockNodeMoveWindows(parent_node->ChildNodes[split_inheritor_child_idx], parent_node);
21698 DockSettingsRenameNodeReferences(parent_node->ID, parent_node->ChildNodes[split_inheritor_child_idx]->ID);
21699 DockNodeUpdateHasCentralNodeChild(DockNodeGetRootNode(parent_node));
21700 DockNodeTreeUpdatePosSize(parent_node, parent_node->Pos, parent_node->Size);
21701
21702 // Flags transfer (e.g. this is where we transfer the ImGuiDockNodeFlags_CentralNode property)
21703 child_0->SharedFlags = parent_node->SharedFlags & ImGuiDockNodeFlags_SharedFlagsInheritMask_;
21704 child_1->SharedFlags = parent_node->SharedFlags & ImGuiDockNodeFlags_SharedFlagsInheritMask_;
21705 child_inheritor->LocalFlags = parent_node->LocalFlags & ImGuiDockNodeFlags_LocalFlagsTransferMask_;
21706 parent_node->LocalFlags &= ~ImGuiDockNodeFlags_LocalFlagsTransferMask_;
21707 child_0->UpdateMergedFlags();
21708 child_1->UpdateMergedFlags();
21709 parent_node->UpdateMergedFlags();
21710 if (child_inheritor->IsCentralNode())
21711 DockNodeGetRootNode(parent_node)->CentralNode = child_inheritor;
21712}
21713
21714void ImGui::DockNodeTreeMerge(ImGuiContext *ctx, ImGuiDockNode *parent_node, ImGuiDockNode *merge_lead_child)
21715{
21716 // When called from DockContextProcessUndockNode() it is possible that one of the child is NULL.
21717 ImGuiContext &g = *GImGui;
21718 ImGuiDockNode *child_0 = parent_node->ChildNodes[0];
21719 ImGuiDockNode *child_1 = parent_node->ChildNodes[1];
21720 IM_ASSERT(child_0 || child_1);
21721 IM_ASSERT(merge_lead_child == child_0 || merge_lead_child == child_1);
21722 if ((child_0 && child_0->Windows.Size > 0) || (child_1 && child_1->Windows.Size > 0))
21723 {
21724 IM_ASSERT(parent_node->TabBar == NULL);
21725 IM_ASSERT(parent_node->Windows.Size == 0);
21726 }
21727 IMGUI_DEBUG_LOG_DOCKING("[docking] DockNodeTreeMerge: 0x%08X + 0x%08X back into parent 0x%08X\n",
21728 child_0 ? child_0->ID : 0, child_1 ? child_1->ID : 0, parent_node->ID);
21729
21730 ImVec2 backup_last_explicit_size = parent_node->SizeRef;
21731 DockNodeMoveChildNodes(parent_node, merge_lead_child);
21732 if (child_0)
21733 {
21734 DockNodeMoveWindows(parent_node, child_0); // Generally only 1 of the 2 child node will have windows
21735 DockSettingsRenameNodeReferences(child_0->ID, parent_node->ID);
21736 }
21737 if (child_1)
21738 {
21739 DockNodeMoveWindows(parent_node, child_1);
21740 DockSettingsRenameNodeReferences(child_1->ID, parent_node->ID);
21741 }
21742 DockNodeApplyPosSizeToWindows(parent_node);
21743 parent_node->AuthorityForPos = parent_node->AuthorityForSize = parent_node->AuthorityForViewport =
21744 ImGuiDataAuthority_Auto;
21745 parent_node->VisibleWindow = merge_lead_child->VisibleWindow;
21746 parent_node->SizeRef = backup_last_explicit_size;
21747
21748 // Flags transfer
21749 parent_node->LocalFlags &= ~ImGuiDockNodeFlags_LocalFlagsTransferMask_; // Preserve Dockspace flag
21750 parent_node->LocalFlags |= (child_0 ? child_0->LocalFlags : 0) & ImGuiDockNodeFlags_LocalFlagsTransferMask_;
21751 parent_node->LocalFlags |= (child_1 ? child_1->LocalFlags : 0) & ImGuiDockNodeFlags_LocalFlagsTransferMask_;
21752 parent_node->LocalFlagsInWindows =
21753 (child_0 ? child_0->LocalFlagsInWindows : 0) |
21754 (child_1 ? child_1->LocalFlagsInWindows : 0); // FIXME: Would be more consistent to update from actual windows
21755 parent_node->UpdateMergedFlags();
21756
21757 if (child_0)
21758 {
21759 ctx->DockContext.Nodes.SetVoidPtr(child_0->ID, NULL);
21760 IM_DELETE(child_0);
21761 }
21762 if (child_1)
21763 {
21764 ctx->DockContext.Nodes.SetVoidPtr(child_1->ID, NULL);
21765 IM_DELETE(child_1);
21766 }
21767}
21768
21769// Update Pos/Size for a node hierarchy (don't affect child Windows yet)
21770// (Depth-first, Pre-Order)
21771void ImGui::DockNodeTreeUpdatePosSize(ImGuiDockNode *node, ImVec2 pos, ImVec2 size,
21772 ImGuiDockNode *only_write_to_single_node)
21773{
21774 // During the regular dock node update we write to all nodes.
21775 // 'only_write_to_single_node' is only set when turning a node visible mid-frame and we need its size right-away.
21776 ImGuiContext &g = *GImGui;
21777 const bool write_to_node = only_write_to_single_node == NULL || only_write_to_single_node == node;
21778 if (write_to_node)
21779 {
21780 node->Pos = pos;
21781 node->Size = size;
21782 }
21783
21784 if (node->IsLeafNode())
21785 return;
21786
21787 ImGuiDockNode *child_0 = node->ChildNodes[0];
21788 ImGuiDockNode *child_1 = node->ChildNodes[1];
21789 ImVec2 child_0_pos = pos, child_1_pos = pos;
21790 ImVec2 child_0_size = size, child_1_size = size;
21791
21792 const bool child_0_is_toward_single_node =
21793 (only_write_to_single_node != NULL && DockNodeIsInHierarchyOf(only_write_to_single_node, child_0));
21794 const bool child_1_is_toward_single_node =
21795 (only_write_to_single_node != NULL && DockNodeIsInHierarchyOf(only_write_to_single_node, child_1));
21796 const bool child_0_is_or_will_be_visible = child_0->IsVisible || child_0_is_toward_single_node;
21797 const bool child_1_is_or_will_be_visible = child_1->IsVisible || child_1_is_toward_single_node;
21798
21799 if (child_0_is_or_will_be_visible && child_1_is_or_will_be_visible)
21800 {
21801 const float spacing = g.Style.DockingSeparatorSize;
21802 const ImGuiAxis axis = (ImGuiAxis)node->SplitAxis;
21803 const float size_avail = ImMax(size[axis] - spacing, 0.0f);
21804
21805 // Size allocation policy
21806 // 1) The first 0..WindowMinSize[axis]*2 are allocated evenly to both windows.
21807 const float size_min_each = ImTrunc(ImMin(size_avail, g.Style.WindowMinSize[axis] * 2.0f) * 0.5f);
21808
21809 // FIXME: Blocks 2) and 3) are essentially doing nearly the same thing.
21810 // Difference are: write-back to SizeRef; application of a minimum size; rounding before ImTrunc()
21811 // Clarify and rework differences between Size & SizeRef and purpose of WantLockSizeOnce
21812
21813 // 2) Process locked absolute size (during a splitter resize we preserve the child of nodes not touching the
21814 // splitter edge)
21815 if (child_0->WantLockSizeOnce && !child_1->WantLockSizeOnce)
21816 {
21817 child_0_size[axis] = child_0->SizeRef[axis] = ImMin(size_avail - 1.0f, child_0->Size[axis]);
21818 child_1_size[axis] = child_1->SizeRef[axis] = (size_avail - child_0_size[axis]);
21819 IM_ASSERT(child_0->SizeRef[axis] > 0.0f && child_1->SizeRef[axis] > 0.0f);
21820 }
21821 else if (child_1->WantLockSizeOnce && !child_0->WantLockSizeOnce)
21822 {
21823 child_1_size[axis] = child_1->SizeRef[axis] = ImMin(size_avail - 1.0f, child_1->Size[axis]);
21824 child_0_size[axis] = child_0->SizeRef[axis] = (size_avail - child_1_size[axis]);
21825 IM_ASSERT(child_0->SizeRef[axis] > 0.0f && child_1->SizeRef[axis] > 0.0f);
21826 }
21827 else if (child_0->WantLockSizeOnce && child_1->WantLockSizeOnce)
21828 {
21829 // FIXME-DOCK: We cannot honor the requested size, so apply ratio.
21830 // Currently this path will only be taken if code programmatically sets WantLockSizeOnce
21831 float split_ratio = child_0_size[axis] / (child_0_size[axis] + child_1_size[axis]);
21832 child_0_size[axis] = child_0->SizeRef[axis] = ImTrunc(size_avail * split_ratio);
21833 child_1_size[axis] = child_1->SizeRef[axis] = (size_avail - child_0_size[axis]);
21834 IM_ASSERT(child_0->SizeRef[axis] > 0.0f && child_1->SizeRef[axis] > 0.0f);
21835 }
21836
21837 // 3) If one window is the central node (~ use remaining space, should be made explicit!), use explicit size
21838 // from the other, and remainder for the central node
21839 else if (child_0->SizeRef[axis] != 0.0f && child_1->HasCentralNodeChild)
21840 {
21841 child_0_size[axis] = ImMin(size_avail - size_min_each, child_0->SizeRef[axis]);
21842 child_1_size[axis] = (size_avail - child_0_size[axis]);
21843 }
21844 else if (child_1->SizeRef[axis] != 0.0f && child_0->HasCentralNodeChild)
21845 {
21846 child_1_size[axis] = ImMin(size_avail - size_min_each, child_1->SizeRef[axis]);
21847 child_0_size[axis] = (size_avail - child_1_size[axis]);
21848 }
21849 else
21850 {
21851 // 4) Otherwise distribute according to the relative ratio of each SizeRef value
21852 float split_ratio = child_0->SizeRef[axis] / (child_0->SizeRef[axis] + child_1->SizeRef[axis]);
21853 child_0_size[axis] = ImMax(size_min_each, ImTrunc(size_avail * split_ratio + 0.5f));
21854 child_1_size[axis] = (size_avail - child_0_size[axis]);
21855 }
21856
21857 child_1_pos[axis] += spacing + child_0_size[axis];
21858 }
21859
21860 if (only_write_to_single_node == NULL)
21861 child_0->WantLockSizeOnce = child_1->WantLockSizeOnce = false;
21862
21863 const bool child_0_recurse = only_write_to_single_node ? child_0_is_toward_single_node : child_0->IsVisible;
21864 const bool child_1_recurse = only_write_to_single_node ? child_1_is_toward_single_node : child_1->IsVisible;
21865 if (child_0_recurse)
21866 DockNodeTreeUpdatePosSize(child_0, child_0_pos, child_0_size);
21867 if (child_1_recurse)
21868 DockNodeTreeUpdatePosSize(child_1, child_1_pos, child_1_size);
21869}
21870
21871static void DockNodeTreeUpdateSplitterFindTouchingNode(ImGuiDockNode *node, ImGuiAxis axis, int side,
21872 ImVector<ImGuiDockNode *> *touching_nodes)
21873{
21874 if (node->IsLeafNode())
21875 {
21876 touching_nodes->push_back(node);
21877 return;
21878 }
21879 if (node->ChildNodes[0]->IsVisible)
21880 if (node->SplitAxis != axis || side == 0 || !node->ChildNodes[1]->IsVisible)
21881 DockNodeTreeUpdateSplitterFindTouchingNode(node->ChildNodes[0], axis, side, touching_nodes);
21882 if (node->ChildNodes[1]->IsVisible)
21883 if (node->SplitAxis != axis || side == 1 || !node->ChildNodes[0]->IsVisible)
21884 DockNodeTreeUpdateSplitterFindTouchingNode(node->ChildNodes[1], axis, side, touching_nodes);
21885}
21886
21887// (Depth-First, Pre-Order)
21888void ImGui::DockNodeTreeUpdateSplitter(ImGuiDockNode *node)
21889{
21890 if (node->IsLeafNode())
21891 return;
21892
21893 ImGuiContext &g = *GImGui;
21894
21895 ImGuiDockNode *child_0 = node->ChildNodes[0];
21896 ImGuiDockNode *child_1 = node->ChildNodes[1];
21897 if (child_0->IsVisible && child_1->IsVisible)
21898 {
21899 // Bounding box of the splitter cover the space between both nodes (w = Spacing, h = Size[xy^1] for when
21900 // splitting horizontally)
21901 const ImGuiAxis axis = (ImGuiAxis)node->SplitAxis;
21902 IM_ASSERT(axis != ImGuiAxis_None);
21903 ImRect bb;
21904 bb.Min = child_0->Pos;
21905 bb.Max = child_1->Pos;
21906 bb.Min[axis] += child_0->Size[axis];
21907 bb.Max[axis ^ 1] += child_1->Size[axis ^ 1];
21908 // if (g.IO.KeyCtrl) GetForegroundDrawList(g.CurrentWindow->Viewport)->AddRect(bb.Min, bb.Max,
21909 // IM_COL32(255,0,255,255));
21910
21911 const ImGuiDockNodeFlags merged_flags =
21912 child_0->MergedFlags | child_1->MergedFlags; // Merged flags for BOTH childs
21913 const ImGuiDockNodeFlags no_resize_axis_flag =
21914 (axis == ImGuiAxis_X) ? ImGuiDockNodeFlags_NoResizeX : ImGuiDockNodeFlags_NoResizeY;
21915 if ((merged_flags & ImGuiDockNodeFlags_NoResize) || (merged_flags & no_resize_axis_flag))
21916 {
21917 ImGuiWindow *window = g.CurrentWindow;
21918 window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_Separator), g.Style.FrameRounding);
21919 }
21920 else
21921 {
21922 // bb.Min[axis] += 1; // Display a little inward so highlight doesn't connect with nearby tabs on the
21923 // neighbor node. bb.Max[axis] -= 1;
21924 PushID(node->ID);
21925
21926 // Find resizing limits by gathering list of nodes that are touching the splitter line.
21927 ImVector<ImGuiDockNode *> touching_nodes[2];
21928 float min_size = g.Style.WindowMinSize[axis];
21929 float resize_limits[2];
21930 resize_limits[0] = node->ChildNodes[0]->Pos[axis] + min_size;
21931 resize_limits[1] = node->ChildNodes[1]->Pos[axis] + node->ChildNodes[1]->Size[axis] - min_size;
21932
21933 ImGuiID splitter_id = GetID("##Splitter");
21934 if (g.ActiveId == splitter_id) // Only process when splitter is active
21935 {
21936 DockNodeTreeUpdateSplitterFindTouchingNode(child_0, axis, 1, &touching_nodes[0]);
21937 DockNodeTreeUpdateSplitterFindTouchingNode(child_1, axis, 0, &touching_nodes[1]);
21938 for (int touching_node_n = 0; touching_node_n < touching_nodes[0].Size; touching_node_n++)
21939 resize_limits[0] =
21940 ImMax(resize_limits[0], touching_nodes[0][touching_node_n]->Rect().Min[axis] + min_size);
21941 for (int touching_node_n = 0; touching_node_n < touching_nodes[1].Size; touching_node_n++)
21942 resize_limits[1] =
21943 ImMin(resize_limits[1], touching_nodes[1][touching_node_n]->Rect().Max[axis] - min_size);
21944
21945 // [DEBUG] Render touching nodes & limits
21946 /*
21947 ImDrawList* draw_list = node->HostWindow ? GetForegroundDrawList(node->HostWindow) :
21948 GetForegroundDrawList(GetMainViewport()); for (int n = 0; n < 2; n++)
21949 {
21950 for (int touching_node_n = 0; touching_node_n < touching_nodes[n].Size; touching_node_n++)
21951 draw_list->AddRect(touching_nodes[n][touching_node_n]->Pos,
21952 touching_nodes[n][touching_node_n]->Pos + touching_nodes[n][touching_node_n]->Size, IM_COL32(0, 255, 0,
21953 255)); if (axis == ImGuiAxis_X) draw_list->AddLine(ImVec2(resize_limits[n], node->ChildNodes[n]->Pos.y),
21954 ImVec2(resize_limits[n], node->ChildNodes[n]->Pos.y + node->ChildNodes[n]->Size.y), IM_COL32(255, 0,
21955 255, 255), 3.0f); else draw_list->AddLine(ImVec2(node->ChildNodes[n]->Pos.x, resize_limits[n]),
21956 ImVec2(node->ChildNodes[n]->Pos.x + node->ChildNodes[n]->Size.x, resize_limits[n]), IM_COL32(255, 0,
21957 255, 255), 3.0f);
21958 }
21959 */
21960 }
21961
21962 // Use a short delay before highlighting the splitter (and changing the mouse cursor) in order for regular
21963 // mouse movement to not highlight many splitters
21964 float cur_size_0 = child_0->Size[axis];
21965 float cur_size_1 = child_1->Size[axis];
21966 float min_size_0 = resize_limits[0] - child_0->Pos[axis];
21967 float min_size_1 = child_1->Pos[axis] + child_1->Size[axis] - resize_limits[1];
21968 ImU32 bg_col = GetColorU32(ImGuiCol_WindowBg);
21969 if (SplitterBehavior(bb, GetID("##Splitter"), axis, &cur_size_0, &cur_size_1, min_size_0, min_size_1,
21970 g.WindowsBorderHoverPadding, WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER, bg_col))
21971 {
21972 if (touching_nodes[0].Size > 0 && touching_nodes[1].Size > 0)
21973 {
21974 child_0->Size[axis] = child_0->SizeRef[axis] = cur_size_0;
21975 child_1->Pos[axis] -= cur_size_1 - child_1->Size[axis];
21976 child_1->Size[axis] = child_1->SizeRef[axis] = cur_size_1;
21977
21978 // Lock the size of every node that is a sibling of the node we are touching
21979 // This might be less desirable if we can merge sibling of a same axis into the same parental level.
21980 for (int side_n = 0; side_n < 2; side_n++)
21981 for (int touching_node_n = 0; touching_node_n < touching_nodes[side_n].Size; touching_node_n++)
21982 {
21983 ImGuiDockNode *touching_node = touching_nodes[side_n][touching_node_n];
21984 // ImDrawList* draw_list = node->HostWindow ? GetForegroundDrawList(node->HostWindow) :
21985 // GetForegroundDrawList(GetMainViewport()); draw_list->AddRect(touching_node->Pos,
21986 // touching_node->Pos + touching_node->Size, IM_COL32(255, 128, 0, 255));
21987 while (touching_node->ParentNode != node)
21988 {
21989 if (touching_node->ParentNode->SplitAxis == axis)
21990 {
21991 // Mark other node so its size will be preserved during the upcoming call to
21992 // DockNodeTreeUpdatePosSize().
21993 ImGuiDockNode *node_to_preserve = touching_node->ParentNode->ChildNodes[side_n];
21994 node_to_preserve->WantLockSizeOnce = true;
21995 // draw_list->AddRect(touching_node->Pos, touching_node->Rect().Max, IM_COL32(255,
21996 // 0, 0, 255)); draw_list->AddRectFilled(node_to_preserve->Pos,
21997 // node_to_preserve->Rect().Max, IM_COL32(0, 255, 0, 100));
21998 }
21999 touching_node = touching_node->ParentNode;
22000 }
22001 }
22002
22003 DockNodeTreeUpdatePosSize(child_0, child_0->Pos, child_0->Size);
22004 DockNodeTreeUpdatePosSize(child_1, child_1->Pos, child_1->Size);
22005 MarkIniSettingsDirty();
22006 }
22007 }
22008 PopID();
22009 }
22010 }
22011
22012 if (child_0->IsVisible)
22013 DockNodeTreeUpdateSplitter(child_0);
22014 if (child_1->IsVisible)
22015 DockNodeTreeUpdateSplitter(child_1);
22016}
22017
22018ImGuiDockNode *ImGui::DockNodeTreeFindFallbackLeafNode(ImGuiDockNode *node)
22019{
22020 if (node->IsLeafNode())
22021 return node;
22022 if (ImGuiDockNode *leaf_node = DockNodeTreeFindFallbackLeafNode(node->ChildNodes[0]))
22023 return leaf_node;
22024 if (ImGuiDockNode *leaf_node = DockNodeTreeFindFallbackLeafNode(node->ChildNodes[1]))
22025 return leaf_node;
22026 return NULL;
22027}
22028
22029ImGuiDockNode *ImGui::DockNodeTreeFindVisibleNodeByPos(ImGuiDockNode *node, ImVec2 pos)
22030{
22031 if (!node->IsVisible)
22032 return NULL;
22033
22034 const float dock_spacing = 0.0f; // g.Style.ItemInnerSpacing.x; // FIXME: Relation to DOCKING_SPLITTER_SIZE?
22035 ImRect r(node->Pos, node->Pos + node->Size);
22036 r.Expand(dock_spacing * 0.5f);
22037 bool inside = r.Contains(pos);
22038 if (!inside)
22039 return NULL;
22040
22041 if (node->IsLeafNode())
22042 return node;
22043 if (ImGuiDockNode *hovered_node = DockNodeTreeFindVisibleNodeByPos(node->ChildNodes[0], pos))
22044 return hovered_node;
22045 if (ImGuiDockNode *hovered_node = DockNodeTreeFindVisibleNodeByPos(node->ChildNodes[1], pos))
22046 return hovered_node;
22047
22048 // This means we are hovering over the splitter/spacing of a parent node
22049 return node;
22050}
22051
22052//-----------------------------------------------------------------------------
22053// Docking: Public Functions (SetWindowDock, DockSpace, DockSpaceOverViewport)
22054//-----------------------------------------------------------------------------
22055// - SetWindowDock() [Internal]
22056// - DockSpace()
22057// - DockSpaceOverViewport()
22058//-----------------------------------------------------------------------------
22059
22060// [Internal] Called via SetNextWindowDockID()
22061void ImGui::SetWindowDock(ImGuiWindow *window, ImGuiID dock_id, ImGuiCond cond)
22062{
22063 // Test condition (NB: bit 0 is always true) and clear flags for next time
22064 if (cond && (window->SetWindowDockAllowFlags & cond) == 0)
22065 return;
22066 window->SetWindowDockAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
22067
22068 if (window->DockId == dock_id)
22069 return;
22070
22071 // If the user attempt to set a dock id that is a split node, we'll dig within to find a suitable docking spot
22072 ImGuiContext &g = *GImGui;
22073 if (ImGuiDockNode *new_node = DockContextFindNodeByID(&g, dock_id))
22074 if (new_node->IsSplitNode())
22075 {
22076 // Policy: Find central node or latest focused node. We first move back to our root node.
22077 new_node = DockNodeGetRootNode(new_node);
22078 if (new_node->CentralNode)
22079 {
22080 IM_ASSERT(new_node->CentralNode->IsCentralNode());
22081 dock_id = new_node->CentralNode->ID;
22082 }
22083 else
22084 {
22085 dock_id = new_node->LastFocusedNodeId;
22086 }
22087 }
22088
22089 if (window->DockId == dock_id)
22090 return;
22091
22092 if (window->DockNode)
22093 DockNodeRemoveWindow(window->DockNode, window, 0);
22094 window->DockId = dock_id;
22095}
22096
22097// Create an explicit dockspace node within an existing window. Also expose dock node flags and creates a CentralNode by
22098// default. The Central Node is always displayed even when empty and shrink/extend according to the requested size of
22099// its neighbors. DockSpace() needs to be submitted _before_ any window they can host. If you use a dockspace, submit it
22100// early in your app. When ImGuiDockNodeFlags_KeepAliveOnly is set, nothing is submitted in the current window (function
22101// may be called from any location).
22102ImGuiID ImGui::DockSpace(ImGuiID dockspace_id, const ImVec2 &size_arg, ImGuiDockNodeFlags flags,
22103 const ImGuiWindowClass *window_class)
22104{
22105 ImGuiContext &g = *GImGui;
22106 ImGuiWindow *window = GetCurrentWindowRead();
22107 if (!(g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable))
22108 return 0;
22109
22110 // Early out if parent window is hidden/collapsed
22111 // This is faster but also DockNodeUpdateTabBar() relies on TabBarLayout() running (which won't if SkipItems=true)
22112 // to set NextSelectedTabId = 0). See #2960. If for whichever reason this is causing problem we would need to ensure
22113 // that DockNodeUpdateTabBar() ends up clearing NextSelectedTabId even if SkipItems=true.
22114 if (window->SkipItems)
22115 flags |= ImGuiDockNodeFlags_KeepAliveOnly;
22116 if ((flags & ImGuiDockNodeFlags_KeepAliveOnly) == 0)
22117 window = GetCurrentWindow(); // call to set window->WriteAccessed = true;
22118
22119 IM_ASSERT((flags & ImGuiDockNodeFlags_DockSpace) ==
22120 0); // Flag is automatically set by DockSpace() as LocalFlags, not SharedFlags!
22121 IM_ASSERT((flags & ImGuiDockNodeFlags_CentralNode) ==
22122 0); // Flag is automatically set by DockSpace() as LocalFlags, not SharedFlags! (#8145)
22123
22124 IM_ASSERT(dockspace_id != 0);
22125 ImGuiDockNode *node = DockContextFindNodeByID(&g, dockspace_id);
22126 if (node == NULL)
22127 {
22128 IMGUI_DEBUG_LOG_DOCKING("[docking] DockSpace: dockspace node 0x%08X created\n", dockspace_id);
22129 node = DockContextAddNode(&g, dockspace_id);
22130 node->SetLocalFlags(ImGuiDockNodeFlags_CentralNode);
22131 }
22132 if (window_class && window_class->ClassId != node->WindowClass.ClassId)
22133 IMGUI_DEBUG_LOG_DOCKING("[docking] DockSpace: dockspace node 0x%08X: setup WindowClass 0x%08X -> 0x%08X\n",
22134 dockspace_id, node->WindowClass.ClassId, window_class->ClassId);
22135 node->SharedFlags = flags;
22136 node->WindowClass = window_class ? *window_class : ImGuiWindowClass();
22137
22138 // When a DockSpace transitioned form implicit to explicit this may be called a second time
22139 // It is possible that the node has already been claimed by a docked window which appeared before the DockSpace()
22140 // node, so we overwrite IsDockSpace again.
22141 if (node->LastFrameActive == g.FrameCount && !(flags & ImGuiDockNodeFlags_KeepAliveOnly))
22142 {
22143 IM_ASSERT(node->IsDockSpace() == false && "Cannot call DockSpace() twice a frame with the same ID");
22144 node->SetLocalFlags(node->LocalFlags | ImGuiDockNodeFlags_DockSpace);
22145 return dockspace_id;
22146 }
22147 node->SetLocalFlags(node->LocalFlags | ImGuiDockNodeFlags_DockSpace);
22148
22149 // Keep alive mode, this is allow windows docked into this node so stay docked even if they are not visible
22150 if (flags & ImGuiDockNodeFlags_KeepAliveOnly)
22151 {
22152 node->LastFrameAlive = g.FrameCount;
22153 return dockspace_id;
22154 }
22155
22156 const ImVec2 content_avail = GetContentRegionAvail();
22157 ImVec2 size = ImTrunc(size_arg);
22158 if (size.x <= 0.0f)
22159 size.x = ImMax(content_avail.x + size.x, 4.0f); // Arbitrary minimum child size (0.0f causing too much issues)
22160 if (size.y <= 0.0f)
22161 size.y = ImMax(content_avail.y + size.y, 4.0f);
22162 IM_ASSERT(size.x > 0.0f && size.y > 0.0f);
22163
22164 node->Pos = window->DC.CursorPos;
22165 node->Size = node->SizeRef = size;
22166 SetNextWindowPos(node->Pos);
22167 SetNextWindowSize(node->Size);
22168 g.NextWindowData.PosUndock = false;
22169
22170 // FIXME-DOCK: Why do we need a child window to host a dockspace, could we host it in the existing window?
22171 // FIXME-DOCK: What is the reason for not simply calling BeginChild()? (OK to have a reason but should be commented)
22172 ImGuiWindowFlags window_flags = ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_DockNodeHost;
22173 window_flags |= ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
22174 ImGuiWindowFlags_NoTitleBar;
22175 window_flags |= ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse;
22176 window_flags |= ImGuiWindowFlags_NoBackground;
22177
22178 char title[256];
22179 ImFormatString(title, IM_ARRAYSIZE(title), "%s/DockSpace_%08X", window->Name, dockspace_id);
22180
22181 PushStyleVar(ImGuiStyleVar_ChildBorderSize, 0.0f);
22182 Begin(title, NULL, window_flags);
22183 PopStyleVar();
22184
22185 ImGuiWindow *host_window = g.CurrentWindow;
22186 DockNodeSetupHostWindow(node, host_window);
22187 host_window->ChildId = window->GetID(title);
22188 node->OnlyNodeWithWindows = NULL;
22189
22190 IM_ASSERT(node->IsRootNode());
22191
22192 // We need to handle the rare case were a central node is missing.
22193 // This can happen if the node was first created manually with DockBuilderAddNode() but _without_ the
22194 // ImGuiDockNodeFlags_Dockspace. Doing it correctly would set the _CentralNode flags, which would then propagate
22195 // according to subsequent split. It would also be ambiguous to attempt to assign a central node while there are
22196 // split nodes, so we wait until there's a single node remaining. The specific sub-property of _CentralNode we are
22197 // interested in recovering here is the "Don't delete when empty" property, as it doesn't make sense for an empty
22198 // dockspace to not have this property.
22199 if (node->IsLeafNode() && !node->IsCentralNode())
22200 node->SetLocalFlags(node->LocalFlags | ImGuiDockNodeFlags_CentralNode);
22201
22202 // Update the node
22203 DockNodeUpdate(node);
22204
22205 End();
22206
22207 ImRect bb(node->Pos, node->Pos + size);
22208 ItemSize(size);
22209 ItemAdd(bb, dockspace_id, NULL,
22210 ImGuiItemFlags_NoNav); // Not a nav point (could be, would need to draw the nav rect and replicate/refactor
22211 // activation from BeginChild(), but seems like CTRL+Tab works better here?)
22212 if ((g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect) &&
22213 IsWindowChildOf(g.HoveredWindow, host_window, false,
22214 true)) // To fullfill IsItemHovered(), similar to EndChild()
22215 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow;
22216
22217 return dockspace_id;
22218}
22219
22220// Tips: Use with ImGuiDockNodeFlags_PassthruCentralNode!
22221// The limitation with this call is that your window won't have a local menu bar, but you can also use
22222// BeginMainMenuBar(). Even though we could pass window flags, it would also require the user to be able to call
22223// BeginMenuBar() somehow meaning we can't Begin/End in a single function. If you really want a menu bar inside the same
22224// window as the one hosting the dockspace, you will need to copy this code somewhere and tweak it.
22225ImGuiID ImGui::DockSpaceOverViewport(ImGuiID dockspace_id, const ImGuiViewport *viewport,
22226 ImGuiDockNodeFlags dockspace_flags, const ImGuiWindowClass *window_class)
22227{
22228 if (viewport == NULL)
22229 viewport = GetMainViewport();
22230
22231 // Submit a window filling the entire viewport
22232 SetNextWindowPos(viewport->WorkPos);
22233 SetNextWindowSize(viewport->WorkSize);
22234 SetNextWindowViewport(viewport->ID);
22235
22236 ImGuiWindowFlags host_window_flags = 0;
22237 host_window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize |
22238 ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDocking;
22239 host_window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;
22240 if (dockspace_flags & ImGuiDockNodeFlags_PassthruCentralNode)
22241 host_window_flags |= ImGuiWindowFlags_NoBackground;
22242
22243 // FIXME-OPT: When using ImGuiDockNodeFlags_KeepAliveOnly with DockSpaceOverViewport() we might be able to spare
22244 // submitting the window, since DockSpace() with that flag doesn't need a window. We'd only need to compute the
22245 // default ID accordingly.
22246 if (dockspace_flags & ImGuiDockNodeFlags_KeepAliveOnly)
22247 host_window_flags |= ImGuiWindowFlags_NoMouseInputs;
22248
22249 char label[32];
22250 ImFormatString(label, IM_ARRAYSIZE(label), "WindowOverViewport_%08X", viewport->ID);
22251
22252 PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
22253 PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
22254 PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
22255 Begin(label, NULL, host_window_flags);
22256 PopStyleVar(3);
22257
22258 // Submit the dockspace
22259 if (dockspace_id == 0)
22260 dockspace_id = GetID("DockSpace");
22261 DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags, window_class);
22262
22263 End();
22264
22265 return dockspace_id;
22266}
22267
22268//-----------------------------------------------------------------------------
22269// Docking: Builder Functions
22270//-----------------------------------------------------------------------------
22271// Very early end-user API to manipulate dock nodes.
22272// Only available in imgui_internal.h. Expect this API to change/break!
22273// It is expected that those functions are all called _before_ the dockspace node submission.
22274//-----------------------------------------------------------------------------
22275// - DockBuilderDockWindow()
22276// - DockBuilderGetNode()
22277// - DockBuilderSetNodePos()
22278// - DockBuilderSetNodeSize()
22279// - DockBuilderAddNode()
22280// - DockBuilderRemoveNode()
22281// - DockBuilderRemoveNodeChildNodes()
22282// - DockBuilderRemoveNodeDockedWindows()
22283// - DockBuilderSplitNode()
22284// - DockBuilderCopyNodeRec()
22285// - DockBuilderCopyNode()
22286// - DockBuilderCopyWindowSettings()
22287// - DockBuilderCopyDockSpace()
22288// - DockBuilderFinish()
22289//-----------------------------------------------------------------------------
22290
22291void ImGui::DockBuilderDockWindow(const char *window_name, ImGuiID node_id)
22292{
22293 // We don't preserve relative order of multiple docked windows (by clearing DockOrder back to -1)
22294 ImGuiContext &g = *GImGui;
22295 IM_UNUSED(g);
22296 IMGUI_DEBUG_LOG_DOCKING("[docking] DockBuilderDockWindow '%s' to node 0x%08X\n", window_name, node_id);
22297 ImGuiID window_id = ImHashStr(window_name);
22298 if (ImGuiWindow *window = FindWindowByID(window_id))
22299 {
22300 // Apply to created window
22301 ImGuiID prev_node_id = window->DockId;
22302 SetWindowDock(window, node_id, ImGuiCond_Always);
22303 if (window->DockId != prev_node_id)
22304 window->DockOrder = -1;
22305 }
22306 else
22307 {
22308 // Apply to settings
22309 ImGuiWindowSettings *settings = FindWindowSettingsByID(window_id);
22310 if (settings == NULL)
22311 settings = CreateNewWindowSettings(window_name);
22312 if (settings->DockId != node_id)
22313 settings->DockOrder = -1;
22314 settings->DockId = node_id;
22315 }
22316}
22317
22318ImGuiDockNode *ImGui::DockBuilderGetNode(ImGuiID node_id)
22319{
22320 ImGuiContext &g = *GImGui;
22321 return DockContextFindNodeByID(&g, node_id);
22322}
22323
22324void ImGui::DockBuilderSetNodePos(ImGuiID node_id, ImVec2 pos)
22325{
22326 ImGuiContext &g = *GImGui;
22327 ImGuiDockNode *node = DockContextFindNodeByID(&g, node_id);
22328 if (node == NULL)
22329 return;
22330 node->Pos = pos;
22331 node->AuthorityForPos = ImGuiDataAuthority_DockNode;
22332}
22333
22334void ImGui::DockBuilderSetNodeSize(ImGuiID node_id, ImVec2 size)
22335{
22336 ImGuiContext &g = *GImGui;
22337 ImGuiDockNode *node = DockContextFindNodeByID(&g, node_id);
22338 if (node == NULL)
22339 return;
22340 IM_ASSERT(size.x > 0.0f && size.y > 0.0f);
22341 node->Size = node->SizeRef = size;
22342 node->AuthorityForSize = ImGuiDataAuthority_DockNode;
22343}
22344
22345// Make sure to use the ImGuiDockNodeFlags_DockSpace flag to create a dockspace node! Otherwise this will create a
22346// floating node!
22347// - Floating node: you can then call DockBuilderSetNodePos()/DockBuilderSetNodeSize() to position and size the floating
22348// node.
22349// - Dockspace node: calling DockBuilderSetNodePos() is unnecessary.
22350// - If you intend to split a node immediately after creation using DockBuilderSplitNode(), make sure to call
22351// DockBuilderSetNodeSize() beforehand!
22352// For various reason, the splitting code currently needs a base size otherwise space may not be allocated as
22353// precisely as you would expect.
22354// - Use (id == 0) to let the system allocate a node identifier.
22355// - Existing node with a same id will be removed.
22356ImGuiID ImGui::DockBuilderAddNode(ImGuiID node_id, ImGuiDockNodeFlags flags)
22357{
22358 ImGuiContext &g = *GImGui;
22359 IM_UNUSED(g);
22360 IMGUI_DEBUG_LOG_DOCKING("[docking] DockBuilderAddNode 0x%08X flags=%08X\n", node_id, flags);
22361
22362 if (node_id != 0)
22363 DockBuilderRemoveNode(node_id);
22364
22365 ImGuiDockNode *node = NULL;
22366 if (flags & ImGuiDockNodeFlags_DockSpace)
22367 {
22368 DockSpace(node_id, ImVec2(0, 0), (flags & ~ImGuiDockNodeFlags_DockSpace) | ImGuiDockNodeFlags_KeepAliveOnly);
22369 node = DockContextFindNodeByID(&g, node_id);
22370 }
22371 else
22372 {
22373 node = DockContextAddNode(&g, node_id);
22374 node->SetLocalFlags(flags);
22375 }
22376 node->LastFrameAlive = g.FrameCount; // Set this otherwise BeginDocked will undock during the same frame.
22377 return node->ID;
22378}
22379
22380void ImGui::DockBuilderRemoveNode(ImGuiID node_id)
22381{
22382 ImGuiContext &g = *GImGui;
22383 IM_UNUSED(g);
22384 IMGUI_DEBUG_LOG_DOCKING("[docking] DockBuilderRemoveNode 0x%08X\n", node_id);
22385
22386 ImGuiDockNode *node = DockContextFindNodeByID(&g, node_id);
22387 if (node == NULL)
22388 return;
22389 DockBuilderRemoveNodeDockedWindows(node_id, true);
22390 DockBuilderRemoveNodeChildNodes(node_id);
22391 // Node may have moved or deleted if e.g. any merge happened
22392 node = DockContextFindNodeByID(&g, node_id);
22393 if (node == NULL)
22394 return;
22395 if (node->IsCentralNode() && node->ParentNode)
22396 node->ParentNode->SetLocalFlags(node->ParentNode->LocalFlags | ImGuiDockNodeFlags_CentralNode);
22397 DockContextRemoveNode(&g, node, true);
22398}
22399
22400// root_id = 0 to remove all, root_id != 0 to remove child of given node.
22401void ImGui::DockBuilderRemoveNodeChildNodes(ImGuiID root_id)
22402{
22403 ImGuiContext &g = *GImGui;
22404 ImGuiDockContext *dc = &g.DockContext;
22405
22406 ImGuiDockNode *root_node = root_id ? DockContextFindNodeByID(&g, root_id) : NULL;
22407 if (root_id && root_node == NULL)
22408 return;
22409 bool has_central_node = false;
22410
22411 ImGuiDataAuthority backup_root_node_authority_for_pos =
22412 root_node ? root_node->AuthorityForPos : ImGuiDataAuthority_Auto;
22413 ImGuiDataAuthority backup_root_node_authority_for_size =
22414 root_node ? root_node->AuthorityForSize : ImGuiDataAuthority_Auto;
22415
22416 // Process active windows
22417 ImVector<ImGuiDockNode *> nodes_to_remove;
22418 for (int n = 0; n < dc->Nodes.Data.Size; n++)
22419 if (ImGuiDockNode *node = (ImGuiDockNode *)dc->Nodes.Data[n].val_p)
22420 {
22421 bool want_removal = (root_id == 0) || (node->ID != root_id && DockNodeGetRootNode(node)->ID == root_id);
22422 if (want_removal)
22423 {
22424 if (node->IsCentralNode())
22425 has_central_node = true;
22426 if (root_id != 0)
22427 DockContextQueueNotifyRemovedNode(&g, node);
22428 if (root_node)
22429 {
22430 DockNodeMoveWindows(root_node, node);
22431 DockSettingsRenameNodeReferences(node->ID, root_node->ID);
22432 }
22433 nodes_to_remove.push_back(node);
22434 }
22435 }
22436
22437 // DockNodeMoveWindows->DockNodeAddWindow will normally set those when reaching two windows (which is only adequate
22438 // during interactive merge) Make sure we don't lose our current pos/size. (FIXME-DOCK: Consider tidying up that
22439 // code in DockNodeAddWindow instead)
22440 if (root_node)
22441 {
22442 root_node->AuthorityForPos = backup_root_node_authority_for_pos;
22443 root_node->AuthorityForSize = backup_root_node_authority_for_size;
22444 }
22445
22446 // Apply to settings
22447 for (ImGuiWindowSettings *settings = g.SettingsWindows.begin(); settings != NULL;
22448 settings = g.SettingsWindows.next_chunk(settings))
22449 if (ImGuiID window_settings_dock_id = settings->DockId)
22450 for (int n = 0; n < nodes_to_remove.Size; n++)
22451 if (nodes_to_remove[n]->ID == window_settings_dock_id)
22452 {
22453 settings->DockId = root_id;
22454 break;
22455 }
22456
22457 // Not really efficient, but easier to destroy a whole hierarchy considering DockContextRemoveNode is attempting to
22458 // merge nodes
22459 if (nodes_to_remove.Size > 1)
22460 ImQsort(nodes_to_remove.Data, nodes_to_remove.Size, sizeof(ImGuiDockNode *), DockNodeComparerDepthMostFirst);
22461 for (int n = 0; n < nodes_to_remove.Size; n++)
22462 DockContextRemoveNode(&g, nodes_to_remove[n], false);
22463
22464 if (root_id == 0)
22465 {
22466 dc->Nodes.Clear();
22467 dc->Requests.clear();
22468 }
22469 else if (has_central_node)
22470 {
22471 root_node->CentralNode = root_node;
22472 root_node->SetLocalFlags(root_node->LocalFlags | ImGuiDockNodeFlags_CentralNode);
22473 }
22474}
22475
22476void ImGui::DockBuilderRemoveNodeDockedWindows(ImGuiID root_id, bool clear_settings_refs)
22477{
22478 // Clear references in settings
22479 ImGuiContext &g = *GImGui;
22480 if (clear_settings_refs)
22481 {
22482 for (ImGuiWindowSettings *settings = g.SettingsWindows.begin(); settings != NULL;
22483 settings = g.SettingsWindows.next_chunk(settings))
22484 {
22485 bool want_removal = (root_id == 0) || (settings->DockId == root_id);
22486 if (!want_removal && settings->DockId != 0)
22487 if (ImGuiDockNode *node = DockContextFindNodeByID(&g, settings->DockId))
22488 if (DockNodeGetRootNode(node)->ID == root_id)
22489 want_removal = true;
22490 if (want_removal)
22491 settings->DockId = 0;
22492 }
22493 }
22494
22495 // Clear references in windows
22496 for (int n = 0; n < g.Windows.Size; n++)
22497 {
22498 ImGuiWindow *window = g.Windows[n];
22499 bool want_removal = (root_id == 0) ||
22500 (window->DockNode && DockNodeGetRootNode(window->DockNode)->ID == root_id) ||
22501 (window->DockNodeAsHost && window->DockNodeAsHost->ID == root_id);
22502 if (want_removal)
22503 {
22504 const ImGuiID backup_dock_id = window->DockId;
22505 IM_UNUSED(backup_dock_id);
22506 DockContextProcessUndockWindow(&g, window, clear_settings_refs);
22507 if (!clear_settings_refs)
22508 IM_ASSERT(window->DockId == backup_dock_id);
22509 }
22510 }
22511}
22512
22513// If 'out_id_at_dir' or 'out_id_at_opposite_dir' are non NULL, the function will write out the ID of the two new nodes
22514// created. Return value is ID of the node at the specified direction, so same as (*out_id_at_dir) if that pointer is
22515// set.
22516// FIXME-DOCK: We are not exposing nor using split_outer.
22517ImGuiID ImGui::DockBuilderSplitNode(ImGuiID id, ImGuiDir split_dir, float size_ratio_for_node_at_dir,
22518 ImGuiID *out_id_at_dir, ImGuiID *out_id_at_opposite_dir)
22519{
22520 ImGuiContext &g = *GImGui;
22521 IM_ASSERT(split_dir != ImGuiDir_None);
22522 IMGUI_DEBUG_LOG_DOCKING("[docking] DockBuilderSplitNode: node 0x%08X, split_dir %d\n", id, split_dir);
22523
22524 ImGuiDockNode *node = DockContextFindNodeByID(&g, id);
22525 if (node == NULL)
22526 {
22527 IM_ASSERT(node != NULL);
22528 return 0;
22529 }
22530
22531 ImGuiDockRequest req;
22532 req.Type = ImGuiDockRequestType_Split;
22533 req.DockTargetWindow = NULL;
22534 req.DockTargetNode = node;
22535 req.DockPayload = NULL;
22536 req.DockSplitDir = split_dir;
22537 req.DockSplitRatio =
22538 ImSaturate((split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Up) ? size_ratio_for_node_at_dir
22539 : 1.0f - size_ratio_for_node_at_dir);
22540 req.DockSplitOuter = false;
22541 DockContextProcessDock(&g, &req);
22542
22543 ImGuiID id_at_dir = node->ChildNodes[(split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Up) ? 0 : 1]->ID;
22544 ImGuiID id_at_opposite_dir = node->ChildNodes[(split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Up) ? 1 : 0]->ID;
22545 if (out_id_at_dir)
22546 *out_id_at_dir = id_at_dir;
22547 if (out_id_at_opposite_dir)
22548 *out_id_at_opposite_dir = id_at_opposite_dir;
22549 return id_at_dir;
22550}
22551
22552static ImGuiDockNode *DockBuilderCopyNodeRec(ImGuiDockNode *src_node, ImGuiID dst_node_id_if_known,
22553 ImVector<ImGuiID> *out_node_remap_pairs)
22554{
22555 ImGuiContext &g = *GImGui;
22556 ImGuiDockNode *dst_node = ImGui::DockContextAddNode(&g, dst_node_id_if_known);
22557 dst_node->SharedFlags = src_node->SharedFlags;
22558 dst_node->LocalFlags = src_node->LocalFlags;
22559 dst_node->LocalFlagsInWindows = ImGuiDockNodeFlags_None;
22560 dst_node->Pos = src_node->Pos;
22561 dst_node->Size = src_node->Size;
22562 dst_node->SizeRef = src_node->SizeRef;
22563 dst_node->SplitAxis = src_node->SplitAxis;
22564 dst_node->UpdateMergedFlags();
22565
22566 out_node_remap_pairs->push_back(src_node->ID);
22567 out_node_remap_pairs->push_back(dst_node->ID);
22568
22569 for (int child_n = 0; child_n < IM_ARRAYSIZE(src_node->ChildNodes); child_n++)
22570 if (src_node->ChildNodes[child_n])
22571 {
22572 dst_node->ChildNodes[child_n] =
22573 DockBuilderCopyNodeRec(src_node->ChildNodes[child_n], 0, out_node_remap_pairs);
22574 dst_node->ChildNodes[child_n]->ParentNode = dst_node;
22575 }
22576
22577 IMGUI_DEBUG_LOG_DOCKING("[docking] Fork node %08X -> %08X (%d childs)\n", src_node->ID, dst_node->ID,
22578 dst_node->IsSplitNode() ? 2 : 0);
22579 return dst_node;
22580}
22581
22582void ImGui::DockBuilderCopyNode(ImGuiID src_node_id, ImGuiID dst_node_id, ImVector<ImGuiID> *out_node_remap_pairs)
22583{
22584 ImGuiContext &g = *GImGui;
22585 IM_ASSERT(src_node_id != 0);
22586 IM_ASSERT(dst_node_id != 0);
22587 IM_ASSERT(out_node_remap_pairs != NULL);
22588
22589 DockBuilderRemoveNode(dst_node_id);
22590
22591 ImGuiDockNode *src_node = DockContextFindNodeByID(&g, src_node_id);
22592 IM_ASSERT(src_node != NULL);
22593
22594 out_node_remap_pairs->clear();
22595 DockBuilderCopyNodeRec(src_node, dst_node_id, out_node_remap_pairs);
22596
22597 IM_ASSERT((out_node_remap_pairs->Size % 2) == 0);
22598}
22599
22600void ImGui::DockBuilderCopyWindowSettings(const char *src_name, const char *dst_name)
22601{
22602 ImGuiWindow *src_window = FindWindowByName(src_name);
22603 if (src_window == NULL)
22604 return;
22605 if (ImGuiWindow *dst_window = FindWindowByName(dst_name))
22606 {
22607 dst_window->Pos = src_window->Pos;
22608 dst_window->Size = src_window->Size;
22609 dst_window->SizeFull = src_window->SizeFull;
22610 dst_window->Collapsed = src_window->Collapsed;
22611 }
22612 else
22613 {
22614 ImGuiWindowSettings *dst_settings = FindWindowSettingsByID(ImHashStr(dst_name));
22615 if (!dst_settings)
22616 dst_settings = CreateNewWindowSettings(dst_name);
22617 ImVec2ih window_pos_2ih = ImVec2ih(src_window->Pos);
22618 if (src_window->ViewportId != 0 && src_window->ViewportId != IMGUI_VIEWPORT_DEFAULT_ID)
22619 {
22620 dst_settings->ViewportPos = window_pos_2ih;
22621 dst_settings->ViewportId = src_window->ViewportId;
22622 dst_settings->Pos = ImVec2ih(0, 0);
22623 }
22624 else
22625 {
22626 dst_settings->Pos = window_pos_2ih;
22627 }
22628 dst_settings->Size = ImVec2ih(src_window->SizeFull);
22629 dst_settings->Collapsed = src_window->Collapsed;
22630 }
22631}
22632
22633// FIXME: Will probably want to change this signature, in particular how the window remapping pairs are passed.
22634void ImGui::DockBuilderCopyDockSpace(ImGuiID src_dockspace_id, ImGuiID dst_dockspace_id,
22635 ImVector<const char *> *in_window_remap_pairs)
22636{
22637 ImGuiContext &g = *GImGui;
22638 IM_ASSERT(src_dockspace_id != 0);
22639 IM_ASSERT(dst_dockspace_id != 0);
22640 IM_ASSERT(in_window_remap_pairs != NULL);
22641 IM_ASSERT((in_window_remap_pairs->Size % 2) == 0);
22642
22643 // Duplicate entire dock
22644 // FIXME: When overwriting dst_dockspace_id, windows that aren't part of our dockspace window class but that are
22645 // docked in a same node will be split apart, whereas we could attempt to at least keep them together in a new, same
22646 // floating node.
22647 ImVector<ImGuiID> node_remap_pairs;
22648 DockBuilderCopyNode(src_dockspace_id, dst_dockspace_id, &node_remap_pairs);
22649
22650 // Attempt to transition all the upcoming windows associated to dst_dockspace_id into the newly created hierarchy of
22651 // dock nodes (The windows associated to src_dockspace_id are staying in place)
22652 ImVector<ImGuiID> src_windows;
22653 for (int remap_window_n = 0; remap_window_n < in_window_remap_pairs->Size; remap_window_n += 2)
22654 {
22655 const char *src_window_name = (*in_window_remap_pairs)[remap_window_n];
22656 const char *dst_window_name = (*in_window_remap_pairs)[remap_window_n + 1];
22657 ImGuiID src_window_id = ImHashStr(src_window_name);
22658 src_windows.push_back(src_window_id);
22659
22660 // Search in the remapping tables
22661 ImGuiID src_dock_id = 0;
22662 if (ImGuiWindow *src_window = FindWindowByID(src_window_id))
22663 src_dock_id = src_window->DockId;
22664 else if (ImGuiWindowSettings *src_window_settings = FindWindowSettingsByID(src_window_id))
22665 src_dock_id = src_window_settings->DockId;
22666 ImGuiID dst_dock_id = 0;
22667 for (int dock_remap_n = 0; dock_remap_n < node_remap_pairs.Size; dock_remap_n += 2)
22668 if (node_remap_pairs[dock_remap_n] == src_dock_id)
22669 {
22670 dst_dock_id = node_remap_pairs[dock_remap_n + 1];
22671 // node_remap_pairs[dock_remap_n] = node_remap_pairs[dock_remap_n + 1] = 0; // Clear
22672 break;
22673 }
22674
22675 if (dst_dock_id != 0)
22676 {
22677 // Docked windows gets redocked into the new node hierarchy.
22678 IMGUI_DEBUG_LOG_DOCKING("[docking] Remap live window '%s' 0x%08X -> '%s' 0x%08X\n", src_window_name,
22679 src_dock_id, dst_window_name, dst_dock_id);
22680 DockBuilderDockWindow(dst_window_name, dst_dock_id);
22681 }
22682 else
22683 {
22684 // Floating windows gets their settings transferred (regardless of whether the new window already exist or
22685 // not) When this is leading to a Copy and not a Move, we would get two overlapping floating windows. Could
22686 // we possibly dock them together?
22687 IMGUI_DEBUG_LOG_DOCKING("[docking] Remap window settings '%s' -> '%s'\n", src_window_name, dst_window_name);
22688 DockBuilderCopyWindowSettings(src_window_name, dst_window_name);
22689 }
22690 }
22691
22692 // Anything else in the source nodes of 'node_remap_pairs' are windows that are not included in the remapping list.
22693 // Find those windows and move to them to the cloned dock node. This may be optional?
22694 // Dock those are a second step as undocking would invalidate source dock nodes.
22695 struct DockRemainingWindowTask
22696 {
22697 ImGuiWindow *Window;
22698 ImGuiID DockId;
22699 DockRemainingWindowTask(ImGuiWindow *window, ImGuiID dock_id)
22700 {
22701 Window = window;
22702 DockId = dock_id;
22703 }
22704 };
22705 ImVector<DockRemainingWindowTask> dock_remaining_windows;
22706 for (int dock_remap_n = 0; dock_remap_n < node_remap_pairs.Size; dock_remap_n += 2)
22707 if (ImGuiID src_dock_id = node_remap_pairs[dock_remap_n])
22708 {
22709 ImGuiID dst_dock_id = node_remap_pairs[dock_remap_n + 1];
22710 ImGuiDockNode *node = DockBuilderGetNode(src_dock_id);
22711 for (int window_n = 0; window_n < node->Windows.Size; window_n++)
22712 {
22713 ImGuiWindow *window = node->Windows[window_n];
22714 if (src_windows.contains(window->ID))
22715 continue;
22716
22717 // Docked windows gets redocked into the new node hierarchy.
22718 IMGUI_DEBUG_LOG_DOCKING("[docking] Remap window '%s' %08X -> %08X\n", window->Name, src_dock_id,
22719 dst_dock_id);
22720 dock_remaining_windows.push_back(DockRemainingWindowTask(window, dst_dock_id));
22721 }
22722 }
22723 for (const DockRemainingWindowTask &task : dock_remaining_windows)
22724 DockBuilderDockWindow(task.Window->Name, task.DockId);
22725}
22726
22727// FIXME-DOCK: This is awkward because in series of split user is likely to loose access to its root node.
22728void ImGui::DockBuilderFinish(ImGuiID root_id)
22729{
22730 ImGuiContext &g = *GImGui;
22731 // DockContextRebuild(&g);
22732 DockContextBuildAddWindowsToNodes(&g, root_id);
22733}
22734
22735//-----------------------------------------------------------------------------
22736// Docking: Begin/End Support Functions (called from Begin/End)
22737//-----------------------------------------------------------------------------
22738// - GetWindowAlwaysWantOwnTabBar()
22739// - DockContextBindNodeToWindow()
22740// - BeginDocked()
22741// - BeginDockableDragDropSource()
22742// - BeginDockableDragDropTarget()
22743//-----------------------------------------------------------------------------
22744
22745bool ImGui::GetWindowAlwaysWantOwnTabBar(ImGuiWindow *window)
22746{
22747 ImGuiContext &g = *GImGui;
22748 if (g.IO.ConfigDockingAlwaysTabBar || window->WindowClass.DockingAlwaysTabBar)
22749 if ((window->Flags &
22750 (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoDocking)) == 0)
22751 if (!window->IsFallbackWindow) // We don't support AlwaysTabBar on the fallback/implicit window to avoid
22752 // unused dock-node overhead/noise
22753 return true;
22754 return false;
22755}
22756
22757static ImGuiDockNode *ImGui::DockContextBindNodeToWindow(ImGuiContext *ctx, ImGuiWindow *window)
22758{
22759 ImGuiContext &g = *ctx;
22760 ImGuiDockNode *node = DockContextFindNodeByID(ctx, window->DockId);
22761 IM_ASSERT(window->DockNode == NULL);
22762
22763 // We should not be docking into a split node (SetWindowDock should avoid this)
22764 if (node && node->IsSplitNode())
22765 {
22766 DockContextProcessUndockWindow(ctx, window);
22767 return NULL;
22768 }
22769
22770 // Create node
22771 if (node == NULL)
22772 {
22773 node = DockContextAddNode(ctx, window->DockId);
22774 node->AuthorityForPos = node->AuthorityForSize = node->AuthorityForViewport = ImGuiDataAuthority_Window;
22775 node->LastFrameAlive = g.FrameCount;
22776 }
22777
22778 // If the node just turned visible and is part of a hierarchy, it doesn't have a Size assigned by
22779 // DockNodeTreeUpdatePosSize() yet, so we're forcing a Pos/Size update from the first ancestor that is already
22780 // visible (often it will be the root node). If we don't do this, the window will be assigned a zero-size on its
22781 // first frame, which won't ideally warm up the layout. This is a little wonky because we don't normally update the
22782 // Pos/Size of visible node mid-frame.
22783 if (!node->IsVisible)
22784 {
22785 ImGuiDockNode *ancestor_node = node;
22786 while (!ancestor_node->IsVisible && ancestor_node->ParentNode)
22787 ancestor_node = ancestor_node->ParentNode;
22788 IM_ASSERT(ancestor_node->Size.x > 0.0f && ancestor_node->Size.y > 0.0f);
22789 DockNodeUpdateHasCentralNodeChild(DockNodeGetRootNode(ancestor_node));
22790 DockNodeTreeUpdatePosSize(ancestor_node, ancestor_node->Pos, ancestor_node->Size, node);
22791 }
22792
22793 // Add window to node
22794 bool node_was_visible = node->IsVisible;
22795 DockNodeAddWindow(node, window, true);
22796 node->IsVisible = node_was_visible; // Don't mark visible right away (so DockContextEndFrame() doesn't render it,
22797 // maybe other side effects? will see)
22798 IM_ASSERT(node == window->DockNode);
22799 return node;
22800}
22801
22802static void StoreDockStyleForWindow(ImGuiWindow *window)
22803{
22804 ImGuiContext &g = *GImGui;
22805 for (int color_n = 0; color_n < ImGuiWindowDockStyleCol_COUNT; color_n++)
22806 window->DockStyle.Colors[color_n] =
22807 ImGui::ColorConvertFloat4ToU32(g.Style.Colors[GWindowDockStyleColors[color_n]]);
22808}
22809
22810void ImGui::BeginDocked(ImGuiWindow *window, bool *p_open)
22811{
22812 ImGuiContext &g = *GImGui;
22813
22814 // Clear fields ahead so most early-out paths don't have to do it
22815 window->DockIsActive = window->DockNodeIsVisible = window->DockTabIsVisible = false;
22816
22817 const bool auto_dock_node = GetWindowAlwaysWantOwnTabBar(window);
22818 if (auto_dock_node)
22819 {
22820 if (window->DockId == 0)
22821 {
22822 IM_ASSERT(window->DockNode == NULL);
22823 window->DockId = DockContextGenNodeID(&g);
22824 }
22825 }
22826 else
22827 {
22828 // Calling SetNextWindowPos() undock windows by default (by setting PosUndock)
22829 bool want_undock = false;
22830 want_undock |= (window->Flags & ImGuiWindowFlags_NoDocking) != 0;
22831 want_undock |= (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasPos) &&
22832 (window->SetWindowPosAllowFlags & g.NextWindowData.PosCond) && g.NextWindowData.PosUndock;
22833 if (want_undock)
22834 {
22835 DockContextProcessUndockWindow(&g, window);
22836 return;
22837 }
22838 }
22839
22840 // Bind to our dock node
22841 ImGuiDockNode *node = window->DockNode;
22842 if (node != NULL)
22843 IM_ASSERT(window->DockId == node->ID);
22844 if (window->DockId != 0 && node == NULL)
22845 {
22846 node = DockContextBindNodeToWindow(&g, window);
22847 if (node == NULL)
22848 return;
22849 }
22850
22851#if 0
22852 // Undock if the ImGuiDockNodeFlags_NoDockingInCentralNode got set
22853 if (node->IsCentralNode && (node->Flags & ImGuiDockNodeFlags_NoDockingInCentralNode))
22854 {
22855 DockContextProcessUndockWindow(ctx, window);
22856 return;
22857 }
22858#endif
22859
22860 // Undock if our dockspace node disappeared
22861 // Note how we are testing for LastFrameAlive and NOT LastFrameActive. A DockSpace node can be maintained alive
22862 // while being inactive with ImGuiDockNodeFlags_KeepAliveOnly.
22863 if (node->LastFrameAlive < g.FrameCount)
22864 {
22865 // If the window has been orphaned, transition the docknode to an implicit node processed in
22866 // DockContextNewFrameUpdateDocking()
22867 ImGuiDockNode *root_node = DockNodeGetRootNode(node);
22868 if (root_node->LastFrameAlive < g.FrameCount)
22869 DockContextProcessUndockWindow(&g, window);
22870 else
22871 window->DockIsActive = true;
22872 return;
22873 }
22874
22875 // Store style overrides
22876 StoreDockStyleForWindow(window);
22877
22878 // Fast path return. It is common for windows to hold on a persistent DockId but be the only visible window,
22879 // and never create neither a host window neither a tab bar.
22880 // FIXME-DOCK: replace ->HostWindow NULL compare with something more explicit (~was initially intended as a first
22881 // frame test)
22882 if (node->HostWindow == NULL)
22883 {
22884 if (node->State == ImGuiDockNodeState_HostWindowHiddenBecauseWindowsAreResizing)
22885 window->DockIsActive = true;
22886 if (node->Windows.Size > 1 && window->Appearing) // Only hide appearing window
22887 DockNodeHideWindowDuringHostWindowCreation(window);
22888 return;
22889 }
22890
22891 // We can have zero-sized nodes (e.g. children of a small-size dockspace)
22892 IM_ASSERT(node->HostWindow);
22893 IM_ASSERT(node->IsLeafNode());
22894 IM_ASSERT(node->Size.x >= 0.0f && node->Size.y >= 0.0f);
22895 node->State = ImGuiDockNodeState_HostWindowVisible;
22896
22897 // Undock if we are submitted earlier than the host window
22898 if (!(node->MergedFlags & ImGuiDockNodeFlags_KeepAliveOnly) &&
22899 window->BeginOrderWithinContext < node->HostWindow->BeginOrderWithinContext)
22900 {
22901 DockContextProcessUndockWindow(&g, window);
22902 return;
22903 }
22904
22905 // Position/Size window
22906 SetNextWindowPos(node->Pos);
22907 SetNextWindowSize(node->Size);
22908 g.NextWindowData.PosUndock = false; // Cancel implicit undocking of SetNextWindowPos()
22909 window->DockIsActive = true;
22910 window->DockNodeIsVisible = true;
22911 window->DockTabIsVisible = false;
22912 if (node->MergedFlags & ImGuiDockNodeFlags_KeepAliveOnly)
22913 return;
22914
22915 // When the window is selected we mark it as visible.
22916 if (node->VisibleWindow == window)
22917 window->DockTabIsVisible = true;
22918
22919 // Update window flag
22920 IM_ASSERT((window->Flags & ImGuiWindowFlags_ChildWindow) == 0);
22921 window->Flags |= ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NoResize;
22922 window->ChildFlags |= ImGuiChildFlags_AlwaysUseWindowPadding;
22923 if (node->IsHiddenTabBar() || node->IsNoTabBar())
22924 window->Flags |= ImGuiWindowFlags_NoTitleBar;
22925 else
22926 window->Flags &= ~ImGuiWindowFlags_NoTitleBar; // Clear the NoTitleBar flag in case the user set it: confusingly
22927 // enough we need a title bar height so we are correctly offset,
22928 // but it won't be displayed!
22929
22930 // Save new dock order only if the window has been visible once already
22931 // This allows multiple windows to be created in the same frame and have their respective dock orders preserved.
22932 if (node->TabBar && window->WasActive)
22933 window->DockOrder = (short)DockNodeGetTabOrder(window);
22934
22935 if ((node->WantCloseAll || node->WantCloseTabId == window->TabId) && p_open != NULL)
22936 *p_open = false;
22937
22938 // Update ChildId to allow returning from Child to Parent with Escape
22939 ImGuiWindow *parent_window = window->DockNode->HostWindow;
22940 window->ChildId = parent_window->GetID(window->Name);
22941}
22942
22943void ImGui::BeginDockableDragDropSource(ImGuiWindow *window)
22944{
22945 ImGuiContext &g = *GImGui;
22946 IM_ASSERT(g.ActiveId == window->MoveId);
22947 IM_ASSERT(g.MovingWindow == window);
22948 IM_ASSERT(g.CurrentWindow == window);
22949
22950 // 0: Hold SHIFT to disable docking, 1: Hold SHIFT to enable docking.
22951 if (g.IO.ConfigDockingWithShift != g.IO.KeyShift)
22952 {
22953 // When ConfigDockingWithShift is set, display a tooltip to increase UI affordance.
22954 // We cannot set for HoveredWindowUnderMovingWindow != NULL here, as it is only valid/useful when drag and drop
22955 // is already active (because of the 'is_mouse_dragging_with_an_expected_destination' logic in
22956 // UpdateViewportsNewFrame() function)
22957 IM_ASSERT(g.NextWindowData.HasFlags == 0);
22958 if (g.IO.ConfigDockingWithShift && g.MouseStationaryTimer >= 1.0f && g.ActiveId >= 1.0f)
22959 SetTooltip("%s", LocalizeGetMsg(ImGuiLocKey_DockingHoldShiftToDock));
22960 return;
22961 }
22962
22963 g.LastItemData.ID = window->MoveId;
22964 window = window->RootWindowDockTree;
22965 IM_ASSERT((window->Flags & ImGuiWindowFlags_NoDocking) == 0);
22966 bool is_drag_docking =
22967 (g.IO.ConfigDockingWithShift) ||
22968 ImRect(0, 0, window->SizeFull.x, GetFrameHeight())
22969 .Contains(g.ActiveIdClickOffset); // FIXME-DOCKING: Need to make this stateful and explicit
22970 ImGuiDragDropFlags drag_drop_flags =
22971 ImGuiDragDropFlags_SourceNoPreviewTooltip | ImGuiDragDropFlags_SourceNoHoldToOpenOthers |
22972 ImGuiDragDropFlags_PayloadAutoExpire | ImGuiDragDropFlags_PayloadNoCrossContext |
22973 ImGuiDragDropFlags_PayloadNoCrossProcess;
22974 if (is_drag_docking && BeginDragDropSource(drag_drop_flags))
22975 {
22976 SetDragDropPayload(IMGUI_PAYLOAD_TYPE_WINDOW, &window, sizeof(window));
22977 EndDragDropSource();
22978 StoreDockStyleForWindow(
22979 window); // Store style overrides while dragging (even when not docked) because docking preview may need it.
22980 }
22981}
22982
22983void ImGui::BeginDockableDragDropTarget(ImGuiWindow *window)
22984{
22985 ImGuiContext &g = *GImGui;
22986
22987 // IM_ASSERT(window->RootWindowDockTree == window); // May also be a DockSpace
22988 IM_ASSERT((window->Flags & ImGuiWindowFlags_NoDocking) == 0);
22989 if (!g.DragDropActive)
22990 return;
22991 // GetForegroundDrawList(window)->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 255, 0, 255));
22992 if (!BeginDragDropTargetCustom(window->Rect(), window->ID))
22993 return;
22994
22995 // Peek into the payload before calling AcceptDragDropPayload() so we can handle overlapping dock nodes with
22996 // filtering (this is a little unusual pattern, normally most code would call AcceptDragDropPayload directly)
22997 const ImGuiPayload *payload = &g.DragDropPayload;
22998 if (!payload->IsDataType(IMGUI_PAYLOAD_TYPE_WINDOW) ||
22999 !DockNodeIsDropAllowed(window, *(ImGuiWindow **)payload->Data))
23000 {
23001 EndDragDropTarget();
23002 return;
23003 }
23004
23005 ImGuiWindow *payload_window = *(ImGuiWindow **)payload->Data;
23006 if (AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_WINDOW,
23007 ImGuiDragDropFlags_AcceptBeforeDelivery | ImGuiDragDropFlags_AcceptNoDrawDefaultRect))
23008 {
23009 // Select target node
23010 // (Important: we cannot use g.HoveredDockNode here! Because each of our target node have filters based on
23011 // payload, each candidate drop target will do its own evaluation)
23012 bool dock_into_floating_window = false;
23013 ImGuiDockNode *node = NULL;
23014 if (window->DockNodeAsHost)
23015 {
23016 // Cannot assume that node will != NULL even though we passed the rectangle test: it depends on
23017 // padding/spacing handled by DockNodeTreeFindVisibleNodeByPos().
23018 node = DockNodeTreeFindVisibleNodeByPos(window->DockNodeAsHost, g.IO.MousePos);
23019
23020 // There is an edge case when docking into a dockspace which only has _inactive_ nodes (because none of the
23021 // windows are active) In this case we need to fallback into any leaf mode, possibly the central node.
23022 // FIXME-20181220: We should not have to test for IsLeafNode() here but we have another bug to fix first.
23023 if (node && node->IsDockSpace() && node->IsRootNode())
23024 node = (node->CentralNode && node->IsLeafNode()) ? node->CentralNode
23025 : DockNodeTreeFindFallbackLeafNode(node);
23026 }
23027 else
23028 {
23029 if (window->DockNode)
23030 node = window->DockNode;
23031 else
23032 dock_into_floating_window = true; // Dock into a regular window
23033 }
23034
23035 const ImRect explicit_target_rect =
23036 (node && node->TabBar && !node->IsHiddenTabBar() && !node->IsNoTabBar())
23037 ? node->TabBar->BarRect
23038 : ImRect(window->Pos, window->Pos + ImVec2(window->Size.x, GetFrameHeight()));
23039 const bool is_explicit_target =
23040 g.IO.ConfigDockingWithShift || IsMouseHoveringRect(explicit_target_rect.Min, explicit_target_rect.Max);
23041
23042 // Preview docking request and find out split direction/ratio
23043 // const bool do_preview = true; // Ignore testing for payload->IsPreview() which removes one frame of
23044 // delay, but breaks overlapping drop targets within the same window.
23045 const bool do_preview = payload->IsPreview() || payload->IsDelivery();
23046 if (do_preview && (node != NULL || dock_into_floating_window))
23047 {
23048 // If we have a non-leaf node it means we are hovering the border of a parent node, in which case only outer
23049 // markers will appear.
23050 ImGuiDockPreviewData split_inner;
23051 ImGuiDockPreviewData split_outer;
23052 ImGuiDockPreviewData *split_data = &split_inner;
23053 if (node && (node->ParentNode || node->IsCentralNode() || !node->IsLeafNode()))
23054 if (ImGuiDockNode *root_node = DockNodeGetRootNode(node))
23055 {
23056 DockNodePreviewDockSetup(window, root_node, payload_window, NULL, &split_outer, is_explicit_target,
23057 true);
23058 if (split_outer.IsSplitDirExplicit)
23059 split_data = &split_outer;
23060 }
23061 if (!node || node->IsLeafNode())
23062 DockNodePreviewDockSetup(window, node, payload_window, NULL, &split_inner, is_explicit_target, false);
23063 if (split_data == &split_outer)
23064 split_inner.IsDropAllowed = false;
23065
23066 // Draw inner then outer, so that previewed tab (in inner data) will be behind the outer drop boxes
23067 DockNodePreviewDockRender(window, node, payload_window, &split_inner);
23068 DockNodePreviewDockRender(window, node, payload_window, &split_outer);
23069
23070 // Queue docking request
23071 if (split_data->IsDropAllowed && payload->IsDelivery())
23072 DockContextQueueDock(&g, window, split_data->SplitNode, payload_window, split_data->SplitDir,
23073 split_data->SplitRatio, split_data == &split_outer);
23074 }
23075 }
23076 EndDragDropTarget();
23077}
23078
23079//-----------------------------------------------------------------------------
23080// Docking: Settings
23081//-----------------------------------------------------------------------------
23082// - DockSettingsRenameNodeReferences()
23083// - DockSettingsRemoveNodeReferences()
23084// - DockSettingsFindNodeSettings()
23085// - DockSettingsHandler_ApplyAll()
23086// - DockSettingsHandler_ReadOpen()
23087// - DockSettingsHandler_ReadLine()
23088// - DockSettingsHandler_DockNodeToSettings()
23089// - DockSettingsHandler_WriteAll()
23090//-----------------------------------------------------------------------------
23091
23092static void ImGui::DockSettingsRenameNodeReferences(ImGuiID old_node_id, ImGuiID new_node_id)
23093{
23094 ImGuiContext &g = *GImGui;
23095 IMGUI_DEBUG_LOG_DOCKING("[docking] DockSettingsRenameNodeReferences: from 0x%08X -> to 0x%08X\n", old_node_id,
23096 new_node_id);
23097 for (int window_n = 0; window_n < g.Windows.Size; window_n++)
23098 {
23099 ImGuiWindow *window = g.Windows[window_n];
23100 if (window->DockId == old_node_id && window->DockNode == NULL)
23101 window->DockId = new_node_id;
23102 }
23104 for (ImGuiWindowSettings *settings = g.SettingsWindows.begin(); settings != NULL;
23105 settings = g.SettingsWindows.next_chunk(settings))
23106 if (settings->DockId == old_node_id)
23107 settings->DockId = new_node_id;
23108}
23109
23110// Remove references stored in ImGuiWindowSettings to the given ImGuiDockNodeSettings
23111static void ImGui::DockSettingsRemoveNodeReferences(ImGuiID *node_ids, int node_ids_count)
23112{
23113 ImGuiContext &g = *GImGui;
23114 int found = 0;
23116 for (ImGuiWindowSettings *settings = g.SettingsWindows.begin(); settings != NULL;
23117 settings = g.SettingsWindows.next_chunk(settings))
23118 for (int node_n = 0; node_n < node_ids_count; node_n++)
23119 if (settings->DockId == node_ids[node_n])
23120 {
23121 settings->DockId = 0;
23122 settings->DockOrder = -1;
23123 if (++found < node_ids_count)
23124 break;
23125 return;
23126 }
23127}
23128
23129static ImGuiDockNodeSettings *ImGui::DockSettingsFindNodeSettings(ImGuiContext *ctx, ImGuiID id)
23130{
23131 // FIXME-OPT
23132 ImGuiDockContext *dc = &ctx->DockContext;
23133 for (int n = 0; n < dc->NodesSettings.Size; n++)
23134 if (dc->NodesSettings[n].ID == id)
23135 return &dc->NodesSettings[n];
23136 return NULL;
23137}
23138
23139// Clear settings data
23140static void ImGui::DockSettingsHandler_ClearAll(ImGuiContext *ctx, ImGuiSettingsHandler *)
23141{
23142 ImGuiDockContext *dc = &ctx->DockContext;
23143 dc->NodesSettings.clear();
23144 DockContextClearNodes(ctx, 0, true);
23145}
23146
23147// Recreate nodes based on settings data
23148static void ImGui::DockSettingsHandler_ApplyAll(ImGuiContext *ctx, ImGuiSettingsHandler *)
23149{
23150 // Prune settings at boot time only
23151 ImGuiDockContext *dc = &ctx->DockContext;
23152 if (ctx->Windows.Size == 0)
23153 DockContextPruneUnusedSettingsNodes(ctx);
23154 DockContextBuildNodesFromSettings(ctx, dc->NodesSettings.Data, dc->NodesSettings.Size);
23155 DockContextBuildAddWindowsToNodes(ctx, 0);
23156}
23157
23158static void *ImGui::DockSettingsHandler_ReadOpen(ImGuiContext *, ImGuiSettingsHandler *, const char *name)
23159{
23160 if (strcmp(name, "Data") != 0)
23161 return NULL;
23162 return (void *)1;
23163}
23164
23165static void ImGui::DockSettingsHandler_ReadLine(ImGuiContext *ctx, ImGuiSettingsHandler *, void *, const char *line)
23166{
23167 char c = 0;
23168 int x = 0, y = 0;
23169 int r = 0;
23170
23171 // Parsing, e.g.
23172 // " DockNode ID=0x00000001 Pos=383,193 Size=201,322 Split=Y,0.506 "
23173 // " DockNode ID=0x00000002 Parent=0x00000001 "
23174 // Important: this code expect currently fields in a fixed order.
23176 line = ImStrSkipBlank(line);
23177 if (strncmp(line, "DockNode", 8) == 0)
23178 {
23179 line = ImStrSkipBlank(line + strlen("DockNode"));
23180 }
23181 else if (strncmp(line, "DockSpace", 9) == 0)
23182 {
23183 line = ImStrSkipBlank(line + strlen("DockSpace"));
23184 node.Flags |= ImGuiDockNodeFlags_DockSpace;
23185 }
23186 else
23187 return;
23188 if (sscanf(line, "ID=0x%08X%n", &node.ID, &r) == 1)
23189 {
23190 line += r;
23191 }
23192 else
23193 return;
23194 if (sscanf(line, " Parent=0x%08X%n", &node.ParentNodeId, &r) == 1)
23195 {
23196 line += r;
23197 if (node.ParentNodeId == 0)
23198 return;
23199 }
23200 if (sscanf(line, " Window=0x%08X%n", &node.ParentWindowId, &r) == 1)
23201 {
23202 line += r;
23203 if (node.ParentWindowId == 0)
23204 return;
23205 }
23206 if (node.ParentNodeId == 0)
23207 {
23208 if (sscanf(line, " Pos=%i,%i%n", &x, &y, &r) == 2)
23209 {
23210 line += r;
23211 node.Pos = ImVec2ih((short)x, (short)y);
23212 }
23213 else
23214 return;
23215 if (sscanf(line, " Size=%i,%i%n", &x, &y, &r) == 2)
23216 {
23217 line += r;
23218 node.Size = ImVec2ih((short)x, (short)y);
23219 }
23220 else
23221 return;
23222 }
23223 else
23224 {
23225 if (sscanf(line, " SizeRef=%i,%i%n", &x, &y, &r) == 2)
23226 {
23227 line += r;
23228 node.SizeRef = ImVec2ih((short)x, (short)y);
23229 }
23230 }
23231 if (sscanf(line, " Split=%c%n", &c, &r) == 1)
23232 {
23233 line += r;
23234 if (c == 'X')
23235 node.SplitAxis = ImGuiAxis_X;
23236 else if (c == 'Y')
23237 node.SplitAxis = ImGuiAxis_Y;
23238 }
23239 if (sscanf(line, " NoResize=%d%n", &x, &r) == 1)
23240 {
23241 line += r;
23242 if (x != 0)
23243 node.Flags |= ImGuiDockNodeFlags_NoResize;
23244 }
23245 if (sscanf(line, " CentralNode=%d%n", &x, &r) == 1)
23246 {
23247 line += r;
23248 if (x != 0)
23249 node.Flags |= ImGuiDockNodeFlags_CentralNode;
23250 }
23251 if (sscanf(line, " NoTabBar=%d%n", &x, &r) == 1)
23252 {
23253 line += r;
23254 if (x != 0)
23255 node.Flags |= ImGuiDockNodeFlags_NoTabBar;
23256 }
23257 if (sscanf(line, " HiddenTabBar=%d%n", &x, &r) == 1)
23258 {
23259 line += r;
23260 if (x != 0)
23261 node.Flags |= ImGuiDockNodeFlags_HiddenTabBar;
23262 }
23263 if (sscanf(line, " NoWindowMenuButton=%d%n", &x, &r) == 1)
23264 {
23265 line += r;
23266 if (x != 0)
23267 node.Flags |= ImGuiDockNodeFlags_NoWindowMenuButton;
23268 }
23269 if (sscanf(line, " NoCloseButton=%d%n", &x, &r) == 1)
23270 {
23271 line += r;
23272 if (x != 0)
23273 node.Flags |= ImGuiDockNodeFlags_NoCloseButton;
23274 }
23275 if (sscanf(line, " Selected=0x%08X%n", &node.SelectedTabId, &r) == 1)
23276 {
23277 line += r;
23278 }
23279 if (node.ParentNodeId != 0)
23280 if (ImGuiDockNodeSettings *parent_settings = DockSettingsFindNodeSettings(ctx, node.ParentNodeId))
23281 node.Depth = parent_settings->Depth + 1;
23282 ctx->DockContext.NodesSettings.push_back(node);
23283}
23284
23285static void DockSettingsHandler_DockNodeToSettings(ImGuiDockContext *dc, ImGuiDockNode *node, int depth)
23286{
23287 ImGuiDockNodeSettings node_settings;
23288 IM_ASSERT(depth < (1 << (sizeof(node_settings.Depth) << 3)));
23289 node_settings.ID = node->ID;
23290 node_settings.ParentNodeId = node->ParentNode ? node->ParentNode->ID : 0;
23291 node_settings.ParentWindowId = (node->IsDockSpace() && node->HostWindow && node->HostWindow->ParentWindow)
23292 ? node->HostWindow->ParentWindow->ID
23293 : 0;
23294 node_settings.SelectedTabId = node->SelectedTabId;
23295 node_settings.SplitAxis = (signed char)(node->IsSplitNode() ? node->SplitAxis : ImGuiAxis_None);
23296 node_settings.Depth = (char)depth;
23297 node_settings.Flags = (node->LocalFlags & ImGuiDockNodeFlags_SavedFlagsMask_);
23298 node_settings.Pos = ImVec2ih(node->Pos);
23299 node_settings.Size = ImVec2ih(node->Size);
23300 node_settings.SizeRef = ImVec2ih(node->SizeRef);
23301 dc->NodesSettings.push_back(node_settings);
23302 if (node->ChildNodes[0])
23303 DockSettingsHandler_DockNodeToSettings(dc, node->ChildNodes[0], depth + 1);
23304 if (node->ChildNodes[1])
23305 DockSettingsHandler_DockNodeToSettings(dc, node->ChildNodes[1], depth + 1);
23306}
23307
23308static void ImGui::DockSettingsHandler_WriteAll(ImGuiContext *ctx, ImGuiSettingsHandler *handler, ImGuiTextBuffer *buf)
23309{
23310 ImGuiContext &g = *ctx;
23311 ImGuiDockContext *dc = &ctx->DockContext;
23312 if (!(g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable))
23313 return;
23314
23315 // Gather settings data
23316 // (unlike our windows settings, because nodes are always built we can do a full rewrite of the SettingsNode buffer)
23317 dc->NodesSettings.resize(0);
23318 dc->NodesSettings.reserve(dc->Nodes.Data.Size);
23319 for (int n = 0; n < dc->Nodes.Data.Size; n++)
23320 if (ImGuiDockNode *node = (ImGuiDockNode *)dc->Nodes.Data[n].val_p)
23321 if (node->IsRootNode())
23322 DockSettingsHandler_DockNodeToSettings(dc, node, 0);
23323
23324 int max_depth = 0;
23325 for (int node_n = 0; node_n < dc->NodesSettings.Size; node_n++)
23326 max_depth = ImMax((int)dc->NodesSettings[node_n].Depth, max_depth);
23327
23328 // Write to text buffer
23329 buf->appendf("[%s][Data]\n", handler->TypeName);
23330 for (int node_n = 0; node_n < dc->NodesSettings.Size; node_n++)
23331 {
23332 const int line_start_pos = buf->size();
23333 (void)line_start_pos;
23334 const ImGuiDockNodeSettings *node_settings = &dc->NodesSettings[node_n];
23335 buf->appendf("%*s%s%*s", node_settings->Depth * 2, "",
23336 (node_settings->Flags & ImGuiDockNodeFlags_DockSpace) ? "DockSpace" : "DockNode ",
23337 (max_depth - node_settings->Depth) * 2, ""); // Text align nodes to facilitate looking at .ini file
23338 buf->appendf(" ID=0x%08X", node_settings->ID);
23339 if (node_settings->ParentNodeId)
23340 {
23341 buf->appendf(" Parent=0x%08X SizeRef=%d,%d", node_settings->ParentNodeId, node_settings->SizeRef.x,
23342 node_settings->SizeRef.y);
23343 }
23344 else
23345 {
23346 if (node_settings->ParentWindowId)
23347 buf->appendf(" Window=0x%08X", node_settings->ParentWindowId);
23348 buf->appendf(" Pos=%d,%d Size=%d,%d", node_settings->Pos.x, node_settings->Pos.y, node_settings->Size.x,
23349 node_settings->Size.y);
23350 }
23351 if (node_settings->SplitAxis != ImGuiAxis_None)
23352 buf->appendf(" Split=%c", (node_settings->SplitAxis == ImGuiAxis_X) ? 'X' : 'Y');
23353 if (node_settings->Flags & ImGuiDockNodeFlags_NoResize)
23354 buf->appendf(" NoResize=1");
23355 if (node_settings->Flags & ImGuiDockNodeFlags_CentralNode)
23356 buf->appendf(" CentralNode=1");
23357 if (node_settings->Flags & ImGuiDockNodeFlags_NoTabBar)
23358 buf->appendf(" NoTabBar=1");
23359 if (node_settings->Flags & ImGuiDockNodeFlags_HiddenTabBar)
23360 buf->appendf(" HiddenTabBar=1");
23361 if (node_settings->Flags & ImGuiDockNodeFlags_NoWindowMenuButton)
23362 buf->appendf(" NoWindowMenuButton=1");
23363 if (node_settings->Flags & ImGuiDockNodeFlags_NoCloseButton)
23364 buf->appendf(" NoCloseButton=1");
23365 if (node_settings->SelectedTabId)
23366 buf->appendf(" Selected=0x%08X", node_settings->SelectedTabId);
23367
23368 // [DEBUG] Include comments in the .ini file to ease debugging (this makes saving slower!)
23369 if (g.IO.ConfigDebugIniSettings)
23370 if (ImGuiDockNode *node = DockContextFindNodeByID(ctx, node_settings->ID))
23371 {
23372 buf->appendf("%*s", ImMax(2, (line_start_pos + 92) - buf->size()), ""); // Align everything
23373 if (node->IsDockSpace() && node->HostWindow && node->HostWindow->ParentWindow)
23374 buf->appendf(" ; in '%s'", node->HostWindow->ParentWindow->Name);
23375 // Iterate settings so we can give info about windows that didn't exist during the session.
23376 int contains_window = 0;
23377 for (ImGuiWindowSettings *settings = g.SettingsWindows.begin(); settings != NULL;
23378 settings = g.SettingsWindows.next_chunk(settings))
23379 if (settings->DockId == node_settings->ID)
23380 {
23381 if (contains_window++ == 0)
23382 buf->appendf(" ; contains ");
23383 buf->appendf("'%s' ", settings->GetName());
23384 }
23385 }
23386
23387 buf->appendf("\n");
23388 }
23389 buf->appendf("\n");
23390}
23391
23392//-----------------------------------------------------------------------------
23393// [SECTION] PLATFORM DEPENDENT HELPERS
23394//-----------------------------------------------------------------------------
23395// - Default clipboard handlers
23396// - Default shell function handlers
23397// - Default IME handlers
23398//-----------------------------------------------------------------------------
23399
23400#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && \
23401 !defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS)
23402
23403#ifdef _MSC_VER
23404#pragma comment(lib, "user32")
23405#pragma comment(lib, "kernel32")
23406#endif
23407
23408// Win32 clipboard implementation
23409// We use g.ClipboardHandlerData for temporary storage to ensure it is freed on Shutdown()
23410static const char *Platform_GetClipboardTextFn_DefaultImpl(ImGuiContext *ctx)
23411{
23412 ImGuiContext &g = *ctx;
23413 g.ClipboardHandlerData.clear();
23414 if (!::OpenClipboard(NULL))
23415 return NULL;
23416 HANDLE wbuf_handle = ::GetClipboardData(CF_UNICODETEXT);
23417 if (wbuf_handle == NULL)
23418 {
23419 ::CloseClipboard();
23420 return NULL;
23421 }
23422 if (const WCHAR *wbuf_global = (const WCHAR *)::GlobalLock(wbuf_handle))
23423 {
23424 int buf_len = ::WideCharToMultiByte(CP_UTF8, 0, wbuf_global, -1, NULL, 0, NULL, NULL);
23425 g.ClipboardHandlerData.resize(buf_len);
23426 ::WideCharToMultiByte(CP_UTF8, 0, wbuf_global, -1, g.ClipboardHandlerData.Data, buf_len, NULL, NULL);
23427 }
23428 ::GlobalUnlock(wbuf_handle);
23429 ::CloseClipboard();
23430 return g.ClipboardHandlerData.Data;
23431}
23432
23433static void Platform_SetClipboardTextFn_DefaultImpl(ImGuiContext *, const char *text)
23434{
23435 if (!::OpenClipboard(NULL))
23436 return;
23437 const int wbuf_length = ::MultiByteToWideChar(CP_UTF8, 0, text, -1, NULL, 0);
23438 HGLOBAL wbuf_handle = ::GlobalAlloc(GMEM_MOVEABLE, (SIZE_T)wbuf_length * sizeof(WCHAR));
23439 if (wbuf_handle == NULL)
23440 {
23441 ::CloseClipboard();
23442 return;
23443 }
23444 WCHAR *wbuf_global = (WCHAR *)::GlobalLock(wbuf_handle);
23445 ::MultiByteToWideChar(CP_UTF8, 0, text, -1, wbuf_global, wbuf_length);
23446 ::GlobalUnlock(wbuf_handle);
23447 ::EmptyClipboard();
23448 if (::SetClipboardData(CF_UNICODETEXT, wbuf_handle) == NULL)
23449 ::GlobalFree(wbuf_handle);
23450 ::CloseClipboard();
23451}
23452
23453#elif defined(__APPLE__) && TARGET_OS_OSX && defined(IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS)
23454
23455#include <Carbon/Carbon.h> // Use old API to avoid need for separate .mm file
23456static PasteboardRef main_clipboard = 0;
23457
23458// OSX clipboard implementation
23459// If you enable this you will need to add '-framework ApplicationServices' to your linker command-line!
23460static void Platform_SetClipboardTextFn_DefaultImpl(ImGuiContext *, const char *text)
23461{
23462 if (!main_clipboard)
23463 PasteboardCreate(kPasteboardClipboard, &main_clipboard);
23464 PasteboardClear(main_clipboard);
23465 CFDataRef cf_data = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)text, ImStrlen(text));
23466 if (cf_data)
23467 {
23468 PasteboardPutItemFlavor(main_clipboard, (PasteboardItemID)1, CFSTR("public.utf8-plain-text"), cf_data, 0);
23469 CFRelease(cf_data);
23470 }
23471}
23472
23473static const char *Platform_GetClipboardTextFn_DefaultImpl(ImGuiContext *ctx)
23474{
23475 ImGuiContext &g = *ctx;
23476 if (!main_clipboard)
23477 PasteboardCreate(kPasteboardClipboard, &main_clipboard);
23478 PasteboardSynchronize(main_clipboard);
23479
23480 ItemCount item_count = 0;
23481 PasteboardGetItemCount(main_clipboard, &item_count);
23482 for (ItemCount i = 0; i < item_count; i++)
23483 {
23484 PasteboardItemID item_id = 0;
23485 PasteboardGetItemIdentifier(main_clipboard, i + 1, &item_id);
23486 CFArrayRef flavor_type_array = 0;
23487 PasteboardCopyItemFlavors(main_clipboard, item_id, &flavor_type_array);
23488 for (CFIndex j = 0, nj = CFArrayGetCount(flavor_type_array); j < nj; j++)
23489 {
23490 CFDataRef cf_data;
23491 if (PasteboardCopyItemFlavorData(main_clipboard, item_id, CFSTR("public.utf8-plain-text"), &cf_data) ==
23492 noErr)
23493 {
23494 g.ClipboardHandlerData.clear();
23495 int length = (int)CFDataGetLength(cf_data);
23496 g.ClipboardHandlerData.resize(length + 1);
23497 CFDataGetBytes(cf_data, CFRangeMake(0, length), (UInt8 *)g.ClipboardHandlerData.Data);
23498 g.ClipboardHandlerData[length] = 0;
23499 CFRelease(cf_data);
23500 return g.ClipboardHandlerData.Data;
23501 }
23502 }
23503 }
23504 return NULL;
23505}
23506
23507#else
23508
23509// Local Dear ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers.
23510static const char *Platform_GetClipboardTextFn_DefaultImpl(ImGuiContext *ctx)
23511{
23512 ImGuiContext &g = *ctx;
23513 return g.ClipboardHandlerData.empty() ? NULL : g.ClipboardHandlerData.begin();
23514}
23515
23516static void Platform_SetClipboardTextFn_DefaultImpl(ImGuiContext *ctx, const char *text)
23517{
23518 ImGuiContext &g = *ctx;
23519 g.ClipboardHandlerData.clear();
23520 const char *text_end = text + ImStrlen(text);
23521 g.ClipboardHandlerData.resize((int)(text_end - text) + 1);
23522 memcpy(&g.ClipboardHandlerData[0], text, (size_t)(text_end - text));
23523 g.ClipboardHandlerData[(int)(text_end - text)] = 0;
23524}
23525
23526#endif // Default clipboard handlers
23527
23528//-----------------------------------------------------------------------------
23529
23530#ifndef IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS
23531#if defined(__APPLE__) && TARGET_OS_IPHONE
23532#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS
23533#endif
23534#if defined(__3DS__)
23535#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS
23536#endif
23537#if defined(_WIN32) && defined(IMGUI_DISABLE_WIN32_FUNCTIONS)
23538#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS
23539#endif
23540#endif // #ifndef IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS
23541
23542#ifndef IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS
23543#ifdef _WIN32
23544#include <shellapi.h> // ShellExecuteA()
23545#ifdef _MSC_VER
23546#pragma comment(lib, "shell32")
23547#endif
23548static bool Platform_OpenInShellFn_DefaultImpl(ImGuiContext *, const char *path)
23549{
23550 const int path_wsize = ::MultiByteToWideChar(CP_UTF8, 0, path, -1, NULL, 0);
23551 ImVector<wchar_t> path_wbuf;
23552 path_wbuf.resize(path_wsize);
23553 ::MultiByteToWideChar(CP_UTF8, 0, path, -1, path_wbuf.Data, path_wsize);
23554 return (INT_PTR)::ShellExecuteW(NULL, L"open", path_wbuf.Data, NULL, NULL, SW_SHOWDEFAULT) > 32;
23555}
23556#else
23557#include <sys/wait.h>
23558#include <unistd.h>
23559static bool Platform_OpenInShellFn_DefaultImpl(ImGuiContext *, const char *path)
23560{
23561#if defined(__APPLE__)
23562 const char *args[]{"open", "--", path, NULL};
23563#else
23564 const char *args[]{"xdg-open", path, NULL};
23565#endif
23566 pid_t pid = fork();
23567 if (pid < 0)
23568 return false;
23569 if (!pid)
23570 {
23571 execvp(args[0], const_cast<char **>(args));
23572 exit(-1);
23573 }
23574 else
23575 {
23576 int status;
23577 waitpid(pid, &status, 0);
23578 return WEXITSTATUS(status) == 0;
23579 }
23580}
23581#endif
23582#else
23583static bool Platform_OpenInShellFn_DefaultImpl(ImGuiContext *, const char *)
23584{
23585 return false;
23586}
23587#endif // Default shell handlers
23588
23589//-----------------------------------------------------------------------------
23590
23591// Win32 API IME support (for Asian languages, etc.)
23592#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS)
23593
23594#include <imm.h>
23595#ifdef _MSC_VER
23596#pragma comment(lib, "imm32")
23597#endif
23598
23599static void Platform_SetImeDataFn_DefaultImpl(ImGuiContext *, ImGuiViewport *viewport, ImGuiPlatformImeData *data)
23600{
23601 // Notify OS Input Method Editor of text input position
23602 HWND hwnd = (HWND)viewport->PlatformHandleRaw;
23603 if (hwnd == 0)
23604 return;
23605
23606 //::ImmAssociateContextEx(hwnd, NULL, data->WantVisible ? IACE_DEFAULT : 0);
23607 if (HIMC himc = ::ImmGetContext(hwnd))
23608 {
23609 COMPOSITIONFORM composition_form = {};
23610 composition_form.ptCurrentPos.x = (LONG)(data->InputPos.x - viewport->Pos.x);
23611 composition_form.ptCurrentPos.y = (LONG)(data->InputPos.y - viewport->Pos.y);
23612 composition_form.dwStyle = CFS_FORCE_POSITION;
23613 ::ImmSetCompositionWindow(himc, &composition_form);
23614 CANDIDATEFORM candidate_form = {};
23615 candidate_form.dwStyle = CFS_CANDIDATEPOS;
23616 candidate_form.ptCurrentPos.x = (LONG)(data->InputPos.x - viewport->Pos.x);
23617 candidate_form.ptCurrentPos.y = (LONG)(data->InputPos.y - viewport->Pos.y);
23618 ::ImmSetCandidateWindow(himc, &candidate_form);
23619 ::ImmReleaseContext(hwnd, himc);
23620 }
23621}
23622
23623#else
23624
23625static void Platform_SetImeDataFn_DefaultImpl(ImGuiContext *, ImGuiViewport *, ImGuiPlatformImeData *)
23626{
23627}
23628
23629#endif // Default IME handlers
23630
23631//-----------------------------------------------------------------------------
23632// [SECTION] METRICS/DEBUGGER WINDOW
23633//-----------------------------------------------------------------------------
23634// - DebugRenderViewportThumbnail() [Internal]
23635// - RenderViewportsThumbnails() [Internal]
23636// - DebugTextEncoding()
23637// - MetricsHelpMarker() [Internal]
23638// - ShowFontAtlas() [Internal]
23639// - ShowMetricsWindow()
23640// - DebugNodeColumns() [Internal]
23641// - DebugNodeDockNode() [Internal]
23642// - DebugNodeDrawList() [Internal]
23643// - DebugNodeDrawCmdShowMeshAndBoundingBox() [Internal]
23644// - DebugNodeFont() [Internal]
23645// - DebugNodeFontGlyph() [Internal]
23646// - DebugNodeStorage() [Internal]
23647// - DebugNodeTabBar() [Internal]
23648// - DebugNodeViewport() [Internal]
23649// - DebugNodeWindow() [Internal]
23650// - DebugNodeWindowSettings() [Internal]
23651// - DebugNodeWindowsList() [Internal]
23652// - DebugNodeWindowsListByBeginStackParent() [Internal]
23653//-----------------------------------------------------------------------------
23654
23655#ifndef IMGUI_DISABLE_DEBUG_TOOLS
23656
23657void ImGui::DebugRenderViewportThumbnail(ImDrawList *draw_list, ImGuiViewportP *viewport, const ImRect &bb)
23658{
23659 ImGuiContext &g = *GImGui;
23660 ImGuiWindow *window = g.CurrentWindow;
23661
23662 ImVec2 scale = bb.GetSize() / viewport->Size;
23663 ImVec2 off = bb.Min - viewport->Pos * scale;
23664 float alpha_mul = (viewport->Flags & ImGuiViewportFlags_IsMinimized) ? 0.30f : 1.00f;
23665 window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border, alpha_mul * 0.40f));
23666 for (ImGuiWindow *thumb_window : g.Windows)
23667 {
23668 if (!thumb_window->WasActive || (thumb_window->Flags & ImGuiWindowFlags_ChildWindow))
23669 continue;
23670 if (thumb_window->Viewport != viewport)
23671 continue;
23672
23673 ImRect thumb_r = thumb_window->Rect();
23674 ImRect title_r = thumb_window->TitleBarRect();
23675 thumb_r = ImRect(ImTrunc(off + thumb_r.Min * scale), ImTrunc(off + thumb_r.Max * scale));
23676 title_r = ImRect(ImTrunc(off + title_r.Min * scale),
23677 ImTrunc(off + ImVec2(title_r.Max.x, title_r.Min.y + title_r.GetHeight() * 3.0f) *
23678 scale)); // Exaggerate title bar height
23679 thumb_r.ClipWithFull(bb);
23680 title_r.ClipWithFull(bb);
23681 const bool window_is_focused = (g.NavWindow && thumb_window->RootWindowForTitleBarHighlight ==
23682 g.NavWindow->RootWindowForTitleBarHighlight);
23683 window->DrawList->AddRectFilled(thumb_r.Min, thumb_r.Max, GetColorU32(ImGuiCol_WindowBg, alpha_mul));
23684 window->DrawList->AddRectFilled(
23685 title_r.Min, title_r.Max,
23686 GetColorU32(window_is_focused ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg, alpha_mul));
23687 window->DrawList->AddRect(thumb_r.Min, thumb_r.Max, GetColorU32(ImGuiCol_Border, alpha_mul));
23688 window->DrawList->AddText(g.Font, g.FontSize * 1.0f, title_r.Min, GetColorU32(ImGuiCol_Text, alpha_mul),
23689 thumb_window->Name, FindRenderedTextEnd(thumb_window->Name));
23690 }
23691 draw_list->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border, alpha_mul));
23692 if (viewport->ID == g.DebugMetricsConfig.HighlightViewportID)
23693 window->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(255, 255, 0, 255));
23694}
23695
23696static void RenderViewportsThumbnails()
23697{
23698 ImGuiContext &g = *GImGui;
23699 ImGuiWindow *window = g.CurrentWindow;
23700
23701 // Draw monitor and calculate their boundaries
23702 float SCALE = 1.0f / 8.0f;
23703 ImRect bb_full(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX);
23704 for (ImGuiPlatformMonitor &monitor : g.PlatformIO.Monitors)
23705 bb_full.Add(ImRect(monitor.MainPos, monitor.MainPos + monitor.MainSize));
23706 ImVec2 p = window->DC.CursorPos;
23707 ImVec2 off = p - bb_full.Min * SCALE;
23708 for (ImGuiPlatformMonitor &monitor : g.PlatformIO.Monitors)
23709 {
23710 ImRect monitor_draw_bb(off + (monitor.MainPos) * SCALE, off + (monitor.MainPos + monitor.MainSize) * SCALE);
23711 window->DrawList->AddRect(
23712 monitor_draw_bb.Min, monitor_draw_bb.Max,
23713 (g.DebugMetricsConfig.HighlightMonitorIdx == g.PlatformIO.Monitors.index_from_ptr(&monitor))
23714 ? IM_COL32(255, 255, 0, 255)
23715 : ImGui::GetColorU32(ImGuiCol_Border),
23716 4.0f);
23717 window->DrawList->AddRectFilled(monitor_draw_bb.Min, monitor_draw_bb.Max,
23718 ImGui::GetColorU32(ImGuiCol_Border, 0.10f), 4.0f);
23719 }
23720
23721 // Draw viewports
23722 for (ImGuiViewportP *viewport : g.Viewports)
23723 {
23724 ImRect viewport_draw_bb(off + (viewport->Pos) * SCALE, off + (viewport->Pos + viewport->Size) * SCALE);
23725 ImGui::DebugRenderViewportThumbnail(window->DrawList, viewport, viewport_draw_bb);
23726 }
23727 ImGui::Dummy(bb_full.GetSize() * SCALE);
23728}
23729
23730static int IMGUI_CDECL ViewportComparerByLastFocusedStampCount(const void *lhs, const void *rhs)
23731{
23732 const ImGuiViewportP *a = *(const ImGuiViewportP *const *)lhs;
23733 const ImGuiViewportP *b = *(const ImGuiViewportP *const *)rhs;
23734 return b->LastFocusedStampCount - a->LastFocusedStampCount;
23735}
23736
23737// Draw an arbitrary US keyboard layout to visualize translated keys
23738void ImGui::DebugRenderKeyboardPreview(ImDrawList *draw_list)
23739{
23740 const float scale = ImGui::GetFontSize() / 13.0f;
23741 const ImVec2 key_size = ImVec2(35.0f, 35.0f) * scale;
23742 const float key_rounding = 3.0f * scale;
23743 const ImVec2 key_face_size = ImVec2(25.0f, 25.0f) * scale;
23744 const ImVec2 key_face_pos = ImVec2(5.0f, 3.0f) * scale;
23745 const float key_face_rounding = 2.0f * scale;
23746 const ImVec2 key_label_pos = ImVec2(7.0f, 4.0f) * scale;
23747 const ImVec2 key_step = ImVec2(key_size.x - 1.0f, key_size.y - 1.0f);
23748 const float key_row_offset = 9.0f * scale;
23749
23750 ImVec2 board_min = GetCursorScreenPos();
23751 ImVec2 board_max =
23752 ImVec2(board_min.x + 3 * key_step.x + 2 * key_row_offset + 10.0f, board_min.y + 3 * key_step.y + 10.0f);
23753 ImVec2 start_pos = ImVec2(board_min.x + 5.0f - key_step.x, board_min.y);
23754
23755 struct KeyLayoutData
23756 {
23757 int Row, Col;
23758 const char *Label;
23759 ImGuiKey Key;
23760 };
23761 const KeyLayoutData keys_to_display[] = {
23762 {0, 0, "", ImGuiKey_Tab}, {0, 1, "Q", ImGuiKey_Q}, {0, 2, "W", ImGuiKey_W},
23763 {0, 3, "E", ImGuiKey_E}, {0, 4, "R", ImGuiKey_R}, {1, 0, "", ImGuiKey_CapsLock},
23764 {1, 1, "A", ImGuiKey_A}, {1, 2, "S", ImGuiKey_S}, {1, 3, "D", ImGuiKey_D},
23765 {1, 4, "F", ImGuiKey_F}, {2, 0, "", ImGuiKey_LeftShift}, {2, 1, "Z", ImGuiKey_Z},
23766 {2, 2, "X", ImGuiKey_X}, {2, 3, "C", ImGuiKey_C}, {2, 4, "V", ImGuiKey_V}};
23767
23768 // Elements rendered manually via ImDrawList API are not clipped automatically.
23769 // While not strictly necessary, here IsItemVisible() is used to avoid rendering these shapes when they are out of
23770 // view.
23771 Dummy(board_max - board_min);
23772 if (!IsItemVisible())
23773 return;
23774 draw_list->PushClipRect(board_min, board_max, true);
23775 for (int n = 0; n < IM_ARRAYSIZE(keys_to_display); n++)
23776 {
23777 const KeyLayoutData *key_data = &keys_to_display[n];
23778 ImVec2 key_min = ImVec2(start_pos.x + key_data->Col * key_step.x + key_data->Row * key_row_offset,
23779 start_pos.y + key_data->Row * key_step.y);
23780 ImVec2 key_max = key_min + key_size;
23781 draw_list->AddRectFilled(key_min, key_max, IM_COL32(204, 204, 204, 255), key_rounding);
23782 draw_list->AddRect(key_min, key_max, IM_COL32(24, 24, 24, 255), key_rounding);
23783 ImVec2 face_min = ImVec2(key_min.x + key_face_pos.x, key_min.y + key_face_pos.y);
23784 ImVec2 face_max = ImVec2(face_min.x + key_face_size.x, face_min.y + key_face_size.y);
23785 draw_list->AddRect(face_min, face_max, IM_COL32(193, 193, 193, 255), key_face_rounding, ImDrawFlags_None, 2.0f);
23786 draw_list->AddRectFilled(face_min, face_max, IM_COL32(252, 252, 252, 255), key_face_rounding);
23787 ImVec2 label_min = ImVec2(key_min.x + key_label_pos.x, key_min.y + key_label_pos.y);
23788 draw_list->AddText(label_min, IM_COL32(64, 64, 64, 255), key_data->Label);
23789 if (IsKeyDown(key_data->Key))
23790 draw_list->AddRectFilled(key_min, key_max, IM_COL32(255, 0, 0, 128), key_rounding);
23791 }
23792 draw_list->PopClipRect();
23793}
23794
23795// Helper tool to diagnose between text encoding issues and font loading issues. Pass your UTF-8 string and verify that
23796// there are correct.
23797void ImGui::DebugTextEncoding(const char *str)
23798{
23799 Text("Text: \"%s\"", str);
23800 if (!BeginTable("##DebugTextEncoding", 4,
23801 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit |
23802 ImGuiTableFlags_Resizable))
23803 return;
23804 TableSetupColumn("Offset");
23805 TableSetupColumn("UTF-8");
23806 TableSetupColumn("Glyph");
23807 TableSetupColumn("Codepoint");
23808 TableHeadersRow();
23809 for (const char *p = str; *p != 0;)
23810 {
23811 unsigned int c;
23812 const int c_utf8_len = ImTextCharFromUtf8(&c, p, NULL);
23813 TableNextColumn();
23814 Text("%d", (int)(p - str));
23815 TableNextColumn();
23816 for (int byte_index = 0; byte_index < c_utf8_len; byte_index++)
23817 {
23818 if (byte_index > 0)
23819 SameLine();
23820 Text("0x%02X", (int)(unsigned char)p[byte_index]);
23821 }
23822 TableNextColumn();
23823 if (GetFont()->FindGlyphNoFallback((ImWchar)c))
23824 TextUnformatted(p, p + c_utf8_len);
23825 else
23826 TextUnformatted((c == IM_UNICODE_CODEPOINT_INVALID) ? "[invalid]" : "[missing]");
23827 TableNextColumn();
23828 Text("U+%04X", (int)c);
23829 p += c_utf8_len;
23830 }
23831 EndTable();
23832}
23833
23834static void DebugFlashStyleColorStop()
23835{
23836 ImGuiContext &g = *GImGui;
23837 if (g.DebugFlashStyleColorIdx != ImGuiCol_COUNT)
23838 g.Style.Colors[g.DebugFlashStyleColorIdx] = g.DebugFlashStyleColorBackup;
23839 g.DebugFlashStyleColorIdx = ImGuiCol_COUNT;
23840}
23841
23842// Flash a given style color for some + inhibit modifications of this color via PushStyleColor() calls.
23843void ImGui::DebugFlashStyleColor(ImGuiCol idx)
23844{
23845 ImGuiContext &g = *GImGui;
23846 DebugFlashStyleColorStop();
23847 g.DebugFlashStyleColorTime = 0.5f;
23848 g.DebugFlashStyleColorIdx = idx;
23849 g.DebugFlashStyleColorBackup = g.Style.Colors[idx];
23850}
23851
23852void ImGui::UpdateDebugToolFlashStyleColor()
23853{
23854 ImGuiContext &g = *GImGui;
23855 if (g.DebugFlashStyleColorTime <= 0.0f)
23856 return;
23857 ColorConvertHSVtoRGB(ImCos(g.DebugFlashStyleColorTime * 6.0f) * 0.5f + 0.5f, 0.5f, 0.5f,
23858 g.Style.Colors[g.DebugFlashStyleColorIdx].x, g.Style.Colors[g.DebugFlashStyleColorIdx].y,
23859 g.Style.Colors[g.DebugFlashStyleColorIdx].z);
23860 g.Style.Colors[g.DebugFlashStyleColorIdx].w = 1.0f;
23861 if ((g.DebugFlashStyleColorTime -= g.IO.DeltaTime) <= 0.0f)
23862 DebugFlashStyleColorStop();
23863}
23864
23865static const char *FormatTextureIDForDebugDisplay(char *buf, int buf_size, ImTextureID tex_id)
23866{
23867 union {
23868 void *ptr;
23869 int integer;
23870 } tex_id_opaque;
23871 memcpy(&tex_id_opaque, &tex_id, ImMin(sizeof(void *), sizeof(tex_id)));
23872 if (sizeof(tex_id) >= sizeof(void *))
23873 ImFormatString(buf, buf_size, "0x%p", tex_id_opaque.ptr);
23874 else
23875 ImFormatString(buf, buf_size, "0x%04X", tex_id_opaque.integer);
23876 return buf;
23877}
23878
23879// Avoid naming collision with imgui_demo.cpp's HelpMarker() for unity builds.
23880static void MetricsHelpMarker(const char *desc)
23881{
23882 ImGui::TextDisabled("(?)");
23883 if (ImGui::BeginItemTooltip())
23884 {
23885 ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
23886 ImGui::TextUnformatted(desc);
23887 ImGui::PopTextWrapPos();
23888 ImGui::EndTooltip();
23889 }
23890}
23891
23892// [DEBUG] List fonts in a font atlas and display its texture
23893void ImGui::ShowFontAtlas(ImFontAtlas *atlas)
23894{
23895 for (ImFont *font : atlas->Fonts)
23896 {
23897 PushID(font);
23898 DebugNodeFont(font);
23899 PopID();
23900 }
23901 if (TreeNode("Font Atlas", "Font Atlas (%dx%d pixels)", atlas->TexWidth, atlas->TexHeight))
23902 {
23903 ImGuiContext &g = *GImGui;
23904 PushStyleVar(ImGuiStyleVar_ImageBorderSize, ImMax(1.0f, g.Style.ImageBorderSize));
23905 ImageWithBg(atlas->TexID, ImVec2((float)atlas->TexWidth, (float)atlas->TexHeight), ImVec2(0.0f, 0.0f),
23906 ImVec2(1.0f, 1.0f), ImVec4(0.0f, 0.0f, 0.0f, 1.0f));
23907 PopStyleVar();
23908 TreePop();
23909 }
23910}
23911
23912void ImGui::ShowMetricsWindow(bool *p_open)
23913{
23914 ImGuiContext &g = *GImGui;
23915 ImGuiIO &io = g.IO;
23916 ImGuiMetricsConfig *cfg = &g.DebugMetricsConfig;
23917 if (cfg->ShowDebugLog)
23918 ShowDebugLogWindow(&cfg->ShowDebugLog);
23919 if (cfg->ShowIDStackTool)
23920 ShowIDStackToolWindow(&cfg->ShowIDStackTool);
23921
23922 if (!Begin("Dear ImGui Metrics/Debugger", p_open) || GetCurrentWindow()->BeginCount > 1)
23923 {
23924 End();
23925 return;
23926 }
23927
23928 // [DEBUG] Clear debug breaks hooks after exactly one cycle.
23929 DebugBreakClearData();
23930
23931 // Basic info
23932 Text("Dear ImGui %s (%d)", IMGUI_VERSION, IMGUI_VERSION_NUM);
23933 if (g.ContextName[0] != 0)
23934 {
23935 SameLine();
23936 Text("(Context Name: \"%s\")", g.ContextName);
23937 }
23938 Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
23939 Text("%d vertices, %d indices (%d triangles)", io.MetricsRenderVertices, io.MetricsRenderIndices,
23940 io.MetricsRenderIndices / 3);
23941 Text("%d visible windows, %d current allocations", io.MetricsRenderWindows,
23942 g.DebugAllocInfo.TotalAllocCount - g.DebugAllocInfo.TotalFreeCount);
23943 // SameLine(); if (SmallButton("GC")) { g.GcCompactAll = true; }
23944
23945 Separator();
23946
23947 // Debugging enums
23948 enum
23949 {
23950 WRT_OuterRect,
23951 WRT_OuterRectClipped,
23952 WRT_InnerRect,
23953 WRT_InnerClipRect,
23954 WRT_WorkRect,
23955 WRT_Content,
23956 WRT_ContentIdeal,
23957 WRT_ContentRegionRect,
23958 WRT_Count
23959 }; // Windows Rect Type
23960 const char *wrt_rects_names[WRT_Count] = {"OuterRect", "OuterRectClipped", "InnerRect", "InnerClipRect",
23961 "WorkRect", "Content", "ContentIdeal", "ContentRegionRect"};
23962 enum
23963 {
23964 TRT_OuterRect,
23965 TRT_InnerRect,
23966 TRT_WorkRect,
23967 TRT_HostClipRect,
23968 TRT_InnerClipRect,
23969 TRT_BackgroundClipRect,
23970 TRT_ColumnsRect,
23971 TRT_ColumnsWorkRect,
23972 TRT_ColumnsClipRect,
23973 TRT_ColumnsContentHeadersUsed,
23974 TRT_ColumnsContentHeadersIdeal,
23975 TRT_ColumnsContentFrozen,
23976 TRT_ColumnsContentUnfrozen,
23977 TRT_Count
23978 }; // Tables Rect Type
23979 const char *trt_rects_names[TRT_Count] = {"OuterRect",
23980 "InnerRect",
23981 "WorkRect",
23982 "HostClipRect",
23983 "InnerClipRect",
23984 "BackgroundClipRect",
23985 "ColumnsRect",
23986 "ColumnsWorkRect",
23987 "ColumnsClipRect",
23988 "ColumnsContentHeadersUsed",
23989 "ColumnsContentHeadersIdeal",
23990 "ColumnsContentFrozen",
23991 "ColumnsContentUnfrozen"};
23992 if (cfg->ShowWindowsRectsType < 0)
23993 cfg->ShowWindowsRectsType = WRT_WorkRect;
23994 if (cfg->ShowTablesRectsType < 0)
23995 cfg->ShowTablesRectsType = TRT_WorkRect;
23996
23997 struct Funcs
23998 {
23999 static ImRect GetTableRect(ImGuiTable *table, int rect_type, int n)
24000 {
24001 ImGuiTableInstanceData *table_instance =
24002 TableGetInstanceData(table, table->InstanceCurrent); // Always using last submitted instance
24003 if (rect_type == TRT_OuterRect)
24004 {
24005 return table->OuterRect;
24006 }
24007 else if (rect_type == TRT_InnerRect)
24008 {
24009 return table->InnerRect;
24010 }
24011 else if (rect_type == TRT_WorkRect)
24012 {
24013 return table->WorkRect;
24014 }
24015 else if (rect_type == TRT_HostClipRect)
24016 {
24017 return table->HostClipRect;
24018 }
24019 else if (rect_type == TRT_InnerClipRect)
24020 {
24021 return table->InnerClipRect;
24022 }
24023 else if (rect_type == TRT_BackgroundClipRect)
24024 {
24025 return table->BgClipRect;
24026 }
24027 else if (rect_type == TRT_ColumnsRect)
24028 {
24029 ImGuiTableColumn *c = &table->Columns[n];
24030 return ImRect(c->MinX, table->InnerClipRect.Min.y, c->MaxX,
24031 table->InnerClipRect.Min.y + table_instance->LastOuterHeight);
24032 }
24033 else if (rect_type == TRT_ColumnsWorkRect)
24034 {
24035 ImGuiTableColumn *c = &table->Columns[n];
24036 return ImRect(c->WorkMinX, table->WorkRect.Min.y, c->WorkMaxX, table->WorkRect.Max.y);
24037 }
24038 else if (rect_type == TRT_ColumnsClipRect)
24039 {
24040 ImGuiTableColumn *c = &table->Columns[n];
24041 return c->ClipRect;
24042 }
24043 else if (rect_type == TRT_ColumnsContentHeadersUsed)
24044 {
24045 ImGuiTableColumn *c = &table->Columns[n];
24046 return ImRect(c->WorkMinX, table->InnerClipRect.Min.y, c->ContentMaxXHeadersUsed,
24047 table->InnerClipRect.Min.y + table_instance->LastTopHeadersRowHeight);
24048 } // Note: y1/y2 not always accurate
24049 else if (rect_type == TRT_ColumnsContentHeadersIdeal)
24050 {
24051 ImGuiTableColumn *c = &table->Columns[n];
24052 return ImRect(c->WorkMinX, table->InnerClipRect.Min.y, c->ContentMaxXHeadersIdeal,
24053 table->InnerClipRect.Min.y + table_instance->LastTopHeadersRowHeight);
24054 }
24055 else if (rect_type == TRT_ColumnsContentFrozen)
24056 {
24057 ImGuiTableColumn *c = &table->Columns[n];
24058 return ImRect(c->WorkMinX, table->InnerClipRect.Min.y, c->ContentMaxXFrozen,
24059 table->InnerClipRect.Min.y + table_instance->LastFrozenHeight);
24060 }
24061 else if (rect_type == TRT_ColumnsContentUnfrozen)
24062 {
24063 ImGuiTableColumn *c = &table->Columns[n];
24064 return ImRect(c->WorkMinX, table->InnerClipRect.Min.y + table_instance->LastFrozenHeight,
24065 c->ContentMaxXUnfrozen, table->InnerClipRect.Max.y);
24066 }
24067 IM_ASSERT(0);
24068 return ImRect();
24069 }
24070
24071 static ImRect GetWindowRect(ImGuiWindow *window, int rect_type)
24072 {
24073 if (rect_type == WRT_OuterRect)
24074 {
24075 return window->Rect();
24076 }
24077 else if (rect_type == WRT_OuterRectClipped)
24078 {
24079 return window->OuterRectClipped;
24080 }
24081 else if (rect_type == WRT_InnerRect)
24082 {
24083 return window->InnerRect;
24084 }
24085 else if (rect_type == WRT_InnerClipRect)
24086 {
24087 return window->InnerClipRect;
24088 }
24089 else if (rect_type == WRT_WorkRect)
24090 {
24091 return window->WorkRect;
24092 }
24093 else if (rect_type == WRT_Content)
24094 {
24095 ImVec2 min = window->InnerRect.Min - window->Scroll + window->WindowPadding;
24096 return ImRect(min, min + window->ContentSize);
24097 }
24098 else if (rect_type == WRT_ContentIdeal)
24099 {
24100 ImVec2 min = window->InnerRect.Min - window->Scroll + window->WindowPadding;
24101 return ImRect(min, min + window->ContentSizeIdeal);
24102 }
24103 else if (rect_type == WRT_ContentRegionRect)
24104 {
24105 return window->ContentRegionRect;
24106 }
24107 IM_ASSERT(0);
24108 return ImRect();
24109 }
24110 };
24111
24112 // Tools
24113 if (TreeNode("Tools"))
24114 {
24115 // Debug Break features
24116 // The Item Picker tool is super useful to visually select an item and break into the call-stack of where it was
24117 // submitted.
24118 SeparatorTextEx(0, "Debug breaks", NULL, CalcTextSize("(?)").x + g.Style.SeparatorTextPadding.x);
24119 SameLine();
24120 MetricsHelpMarker("Will call the IM_DEBUG_BREAK() macro to break in debugger.\nWarning: If you don't have a "
24121 "debugger attached, this will probably crash.");
24122 if (Checkbox("Show Item Picker", &g.DebugItemPickerActive) && g.DebugItemPickerActive)
24123 DebugStartItemPicker();
24124 Checkbox("Show \"Debug Break\" buttons in other sections (io.ConfigDebugIsDebuggerPresent)",
24125 &g.IO.ConfigDebugIsDebuggerPresent);
24126
24127 SeparatorText("Visualize");
24128
24129 Checkbox("Show Debug Log", &cfg->ShowDebugLog);
24130 SameLine();
24131 MetricsHelpMarker("You can also call ImGui::ShowDebugLogWindow() from your code.");
24132
24133 Checkbox("Show ID Stack Tool", &cfg->ShowIDStackTool);
24134 SameLine();
24135 MetricsHelpMarker("You can also call ImGui::ShowIDStackToolWindow() from your code.");
24136
24137 Checkbox("Show windows begin order", &cfg->ShowWindowsBeginOrder);
24138 Checkbox("Show windows rectangles", &cfg->ShowWindowsRects);
24139 SameLine();
24140 SetNextItemWidth(GetFontSize() * 12);
24141 cfg->ShowWindowsRects |=
24142 Combo("##show_windows_rect_type", &cfg->ShowWindowsRectsType, wrt_rects_names, WRT_Count, WRT_Count);
24143 if (cfg->ShowWindowsRects && g.NavWindow != NULL)
24144 {
24145 BulletText("'%s':", g.NavWindow->Name);
24146 Indent();
24147 for (int rect_n = 0; rect_n < WRT_Count; rect_n++)
24148 {
24149 ImRect r = Funcs::GetWindowRect(g.NavWindow, rect_n);
24150 Text("(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) %s", r.Min.x, r.Min.y, r.Max.x, r.Max.y,
24151 r.GetWidth(), r.GetHeight(), wrt_rects_names[rect_n]);
24152 }
24153 Unindent();
24154 }
24155
24156 Checkbox("Show tables rectangles", &cfg->ShowTablesRects);
24157 SameLine();
24158 SetNextItemWidth(GetFontSize() * 12);
24159 cfg->ShowTablesRects |=
24160 Combo("##show_table_rects_type", &cfg->ShowTablesRectsType, trt_rects_names, TRT_Count, TRT_Count);
24161 if (cfg->ShowTablesRects && g.NavWindow != NULL)
24162 {
24163 for (int table_n = 0; table_n < g.Tables.GetMapSize(); table_n++)
24164 {
24165 ImGuiTable *table = g.Tables.TryGetMapData(table_n);
24166 if (table == NULL || table->LastFrameActive < g.FrameCount - 1 ||
24167 (table->OuterWindow != g.NavWindow && table->InnerWindow != g.NavWindow))
24168 continue;
24169
24170 BulletText("Table 0x%08X (%d columns, in '%s')", table->ID, table->ColumnsCount,
24171 table->OuterWindow->Name);
24172 if (IsItemHovered())
24173 GetForegroundDrawList()->AddRect(table->OuterRect.Min - ImVec2(1, 1),
24174 table->OuterRect.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255),
24175 0.0f, 0, 2.0f);
24176 Indent();
24177 char buf[128];
24178 for (int rect_n = 0; rect_n < TRT_Count; rect_n++)
24179 {
24180 if (rect_n >= TRT_ColumnsRect)
24181 {
24182 if (rect_n != TRT_ColumnsRect && rect_n != TRT_ColumnsClipRect)
24183 continue;
24184 for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
24185 {
24186 ImRect r = Funcs::GetTableRect(table, rect_n, column_n);
24187 ImFormatString(buf, IM_ARRAYSIZE(buf),
24188 "(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) Col %d %s", r.Min.x, r.Min.y,
24189 r.Max.x, r.Max.y, r.GetWidth(), r.GetHeight(), column_n,
24190 trt_rects_names[rect_n]);
24191 Selectable(buf);
24192 if (IsItemHovered())
24193 GetForegroundDrawList()->AddRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1),
24194 IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f);
24195 }
24196 }
24197 else
24198 {
24199 ImRect r = Funcs::GetTableRect(table, rect_n, -1);
24200 ImFormatString(buf, IM_ARRAYSIZE(buf), "(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) %s",
24201 r.Min.x, r.Min.y, r.Max.x, r.Max.y, r.GetWidth(), r.GetHeight(),
24202 trt_rects_names[rect_n]);
24203 Selectable(buf);
24204 if (IsItemHovered())
24205 GetForegroundDrawList()->AddRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1),
24206 IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f);
24207 }
24208 }
24209 Unindent();
24210 }
24211 }
24212 Checkbox(
24213 "Show groups rectangles",
24214 &g.DebugShowGroupRects); // Storing in context as this is used by group code and prefers to be in hot-data
24215
24216 SeparatorText("Validate");
24217
24218 Checkbox("Debug Begin/BeginChild return value", &io.ConfigDebugBeginReturnValueLoop);
24219 SameLine();
24220 MetricsHelpMarker("Some calls to Begin()/BeginChild() will return false.\n\nWill cycle through window depths "
24221 "then repeat. Windows should be flickering while running.");
24222
24223 Checkbox("UTF-8 Encoding viewer", &cfg->ShowTextEncodingViewer);
24224 SameLine();
24225 MetricsHelpMarker("You can also call ImGui::DebugTextEncoding() from your code with a given string to test "
24226 "that your UTF-8 encoding settings are correct.");
24227 if (cfg->ShowTextEncodingViewer)
24228 {
24229 static char buf[64] = "";
24230 SetNextItemWidth(-FLT_MIN);
24231 InputText("##DebugTextEncodingBuf", buf, IM_ARRAYSIZE(buf));
24232 if (buf[0] != 0)
24233 DebugTextEncoding(buf);
24234 }
24235
24236 TreePop();
24237 }
24238
24239 // Windows
24240 if (TreeNode("Windows", "Windows (%d)", g.Windows.Size))
24241 {
24242 // SetNextItemOpen(true, ImGuiCond_Once);
24243 DebugNodeWindowsList(&g.Windows, "By display order");
24244 DebugNodeWindowsList(&g.WindowsFocusOrder, "By focus order (root windows)");
24245 if (TreeNode("By submission order (begin stack)"))
24246 {
24247 // Here we display windows in their submitted order/hierarchy, however note that the Begin stack doesn't
24248 // constitute a Parent<>Child relationship!
24249 ImVector<ImGuiWindow *> &temp_buffer = g.WindowsTempSortBuffer;
24250 temp_buffer.resize(0);
24251 for (ImGuiWindow *window : g.Windows)
24252 if (window->LastFrameActive + 1 >= g.FrameCount)
24253 temp_buffer.push_back(window);
24254 struct Func
24255 {
24256 static int IMGUI_CDECL WindowComparerByBeginOrder(const void *lhs, const void *rhs)
24257 {
24258 return ((int)(*(const ImGuiWindow *const *)lhs)->BeginOrderWithinContext -
24259 (*(const ImGuiWindow *const *)rhs)->BeginOrderWithinContext);
24260 }
24261 };
24262 ImQsort(temp_buffer.Data, (size_t)temp_buffer.Size, sizeof(ImGuiWindow *),
24263 Func::WindowComparerByBeginOrder);
24264 DebugNodeWindowsListByBeginStackParent(temp_buffer.Data, temp_buffer.Size, NULL);
24265 TreePop();
24266 }
24267
24268 TreePop();
24269 }
24270
24271 // DrawLists
24272 int drawlist_count = 0;
24273 for (ImGuiViewportP *viewport : g.Viewports)
24274 drawlist_count += viewport->DrawDataP.CmdLists.Size;
24275 if (TreeNode("DrawLists", "DrawLists (%d)", drawlist_count))
24276 {
24277 Checkbox("Show ImDrawCmd mesh when hovering", &cfg->ShowDrawCmdMesh);
24278 Checkbox("Show ImDrawCmd bounding boxes when hovering", &cfg->ShowDrawCmdBoundingBoxes);
24279 for (ImGuiViewportP *viewport : g.Viewports)
24280 {
24281 bool viewport_has_drawlist = false;
24282 for (ImDrawList *draw_list : viewport->DrawDataP.CmdLists)
24283 {
24284 if (!viewport_has_drawlist)
24285 Text("Active DrawLists in Viewport #%d, ID: 0x%08X", viewport->Idx, viewport->ID);
24286 viewport_has_drawlist = true;
24287 DebugNodeDrawList(NULL, viewport, draw_list, "DrawList");
24288 }
24289 }
24290 TreePop();
24291 }
24292
24293 // Viewports
24294 if (TreeNode("Viewports", "Viewports (%d)", g.Viewports.Size))
24295 {
24296 cfg->HighlightMonitorIdx = -1;
24297 bool open = TreeNode("Monitors", "Monitors (%d)", g.PlatformIO.Monitors.Size);
24298 SameLine();
24299 MetricsHelpMarker("Dear ImGui uses monitor data:\n- to query DPI settings on a per monitor basis\n- to "
24300 "position popup/tooltips so they don't straddle monitors.");
24301 if (open)
24302 {
24303 for (int i = 0; i < g.PlatformIO.Monitors.Size; i++)
24304 {
24305 DebugNodePlatformMonitor(&g.PlatformIO.Monitors[i], "Monitor", i);
24306 if (IsItemHovered())
24307 cfg->HighlightMonitorIdx = i;
24308 }
24309 DebugNodePlatformMonitor(&g.FallbackMonitor, "Fallback", 0);
24310 TreePop();
24311 }
24312
24313 SetNextItemOpen(true, ImGuiCond_Once);
24314 if (TreeNode("Windows Minimap"))
24315 {
24316 RenderViewportsThumbnails();
24317 TreePop();
24318 }
24319 cfg->HighlightViewportID = 0;
24320
24321 BulletText("MouseViewport: 0x%08X (UserHovered 0x%08X, LastHovered 0x%08X)",
24322 g.MouseViewport ? g.MouseViewport->ID : 0, g.IO.MouseHoveredViewport,
24323 g.MouseLastHoveredViewport ? g.MouseLastHoveredViewport->ID : 0);
24324 if (TreeNode("Inferred Z order (front-to-back)"))
24325 {
24326 static ImVector<ImGuiViewportP *> viewports;
24327 viewports.resize(g.Viewports.Size);
24328 memcpy(viewports.Data, g.Viewports.Data, g.Viewports.size_in_bytes());
24329 if (viewports.Size > 1)
24330 ImQsort(viewports.Data, viewports.Size, sizeof(ImGuiViewport *),
24331 ViewportComparerByLastFocusedStampCount);
24332 for (ImGuiViewportP *viewport : viewports)
24333 {
24334 BulletText("Viewport #%d, ID: 0x%08X, LastFocused = %08d, PlatformFocused = %s, Window: \"%s\"",
24335 viewport->Idx, viewport->ID, viewport->LastFocusedStampCount,
24336 (g.PlatformIO.Platform_GetWindowFocus && viewport->PlatformWindowCreated)
24337 ? (g.PlatformIO.Platform_GetWindowFocus(viewport) ? "1" : "0")
24338 : "N/A",
24339 viewport->Window ? viewport->Window->Name : "N/A");
24340 if (IsItemHovered())
24341 cfg->HighlightViewportID = viewport->ID;
24342 }
24343 TreePop();
24344 }
24345
24346 for (ImGuiViewportP *viewport : g.Viewports)
24347 DebugNodeViewport(viewport);
24348 TreePop();
24349 }
24350
24351 // Details for Popups
24352 if (TreeNode("Popups", "Popups (%d)", g.OpenPopupStack.Size))
24353 {
24354 for (const ImGuiPopupData &popup_data : g.OpenPopupStack)
24355 {
24356 // As it's difficult to interact with tree nodes while popups are open, we display everything inline.
24357 ImGuiWindow *window = popup_data.Window;
24358 BulletText("PopupID: %08x, Window: '%s' (%s%s), RestoreNavWindow '%s', ParentWindow '%s'",
24359 popup_data.PopupId, window ? window->Name : "NULL",
24360 window && (window->Flags & ImGuiWindowFlags_ChildWindow) ? "Child;" : "",
24361 window && (window->Flags & ImGuiWindowFlags_ChildMenu) ? "Menu;" : "",
24362 popup_data.RestoreNavWindow ? popup_data.RestoreNavWindow->Name : "NULL",
24363 window && window->ParentWindow ? window->ParentWindow->Name : "NULL");
24364 }
24365 TreePop();
24366 }
24367
24368 // Details for TabBars
24369 if (TreeNode("TabBars", "Tab Bars (%d)", g.TabBars.GetAliveCount()))
24370 {
24371 for (int n = 0; n < g.TabBars.GetMapSize(); n++)
24372 if (ImGuiTabBar *tab_bar = g.TabBars.TryGetMapData(n))
24373 {
24374 PushID(tab_bar);
24375 DebugNodeTabBar(tab_bar, "TabBar");
24376 PopID();
24377 }
24378 TreePop();
24379 }
24380
24381 // Details for Tables
24382 if (TreeNode("Tables", "Tables (%d)", g.Tables.GetAliveCount()))
24383 {
24384 for (int n = 0; n < g.Tables.GetMapSize(); n++)
24385 if (ImGuiTable *table = g.Tables.TryGetMapData(n))
24386 DebugNodeTable(table);
24387 TreePop();
24388 }
24389
24390 // Details for Fonts
24391 ImFontAtlas *atlas = g.IO.Fonts;
24392 if (TreeNode("Fonts", "Fonts (%d)", atlas->Fonts.Size))
24393 {
24394 ShowFontAtlas(atlas);
24395 TreePop();
24396 }
24397
24398 // Details for InputText
24399 if (TreeNode("InputText"))
24400 {
24401 DebugNodeInputTextState(&g.InputTextState);
24402 TreePop();
24403 }
24404
24405 // Details for TypingSelect
24406 if (TreeNode("TypingSelect", "TypingSelect (%d)", g.TypingSelectState.SearchBuffer[0] != 0 ? 1 : 0))
24407 {
24408 DebugNodeTypingSelectState(&g.TypingSelectState);
24409 TreePop();
24410 }
24411
24412 // Details for MultiSelect
24413 if (TreeNode("MultiSelect", "MultiSelect (%d)", g.MultiSelectStorage.GetAliveCount()))
24414 {
24415 ImGuiBoxSelectState *bs = &g.BoxSelectState;
24416 BulletText("BoxSelect ID=0x%08X, Starting = %d, Active %d", bs->ID, bs->IsStarting, bs->IsActive);
24417 for (int n = 0; n < g.MultiSelectStorage.GetMapSize(); n++)
24418 if (ImGuiMultiSelectState *state = g.MultiSelectStorage.TryGetMapData(n))
24419 DebugNodeMultiSelectState(state);
24420 TreePop();
24421 }
24422
24423 // Details for Docking
24424#ifdef IMGUI_HAS_DOCK
24425 if (TreeNode("Docking"))
24426 {
24427 static bool root_nodes_only = true;
24428 ImGuiDockContext *dc = &g.DockContext;
24429 Checkbox("List root nodes", &root_nodes_only);
24430 Checkbox("Ctrl shows window dock info", &cfg->ShowDockingNodes);
24431 if (SmallButton("Clear nodes"))
24432 {
24433 DockContextClearNodes(&g, 0, true);
24434 }
24435 SameLine();
24436 if (SmallButton("Rebuild all"))
24437 {
24438 dc->WantFullRebuild = true;
24439 }
24440 for (int n = 0; n < dc->Nodes.Data.Size; n++)
24441 if (ImGuiDockNode *node = (ImGuiDockNode *)dc->Nodes.Data[n].val_p)
24442 if (!root_nodes_only || node->IsRootNode())
24443 DebugNodeDockNode(node, "Node");
24444 TreePop();
24445 }
24446#endif // #ifdef IMGUI_HAS_DOCK
24447
24448 // Settings
24449 if (TreeNode("Settings"))
24450 {
24451 if (SmallButton("Clear"))
24452 ClearIniSettings();
24453 SameLine();
24454 if (SmallButton("Save to memory"))
24455 SaveIniSettingsToMemory();
24456 SameLine();
24457 if (SmallButton("Save to disk"))
24458 SaveIniSettingsToDisk(g.IO.IniFilename);
24459 SameLine();
24460 if (g.IO.IniFilename)
24461 Text("\"%s\"", g.IO.IniFilename);
24462 else
24463 TextUnformatted("<NULL>");
24464 Checkbox("io.ConfigDebugIniSettings", &io.ConfigDebugIniSettings);
24465 Text("SettingsDirtyTimer %.2f", g.SettingsDirtyTimer);
24466 if (TreeNode("SettingsHandlers", "Settings handlers: (%d)", g.SettingsHandlers.Size))
24467 {
24468 for (ImGuiSettingsHandler &handler : g.SettingsHandlers)
24469 BulletText("\"%s\"", handler.TypeName);
24470 TreePop();
24471 }
24472 if (TreeNode("SettingsWindows", "Settings packed data: Windows: %d bytes", g.SettingsWindows.size()))
24473 {
24474 for (ImGuiWindowSettings *settings = g.SettingsWindows.begin(); settings != NULL;
24475 settings = g.SettingsWindows.next_chunk(settings))
24476 DebugNodeWindowSettings(settings);
24477 TreePop();
24478 }
24479
24480 if (TreeNode("SettingsTables", "Settings packed data: Tables: %d bytes", g.SettingsTables.size()))
24481 {
24482 for (ImGuiTableSettings *settings = g.SettingsTables.begin(); settings != NULL;
24483 settings = g.SettingsTables.next_chunk(settings))
24484 DebugNodeTableSettings(settings);
24485 TreePop();
24486 }
24487
24488#ifdef IMGUI_HAS_DOCK
24489 if (TreeNode("SettingsDocking", "Settings packed data: Docking"))
24490 {
24491 ImGuiDockContext *dc = &g.DockContext;
24492 Text("In SettingsWindows:");
24493 for (ImGuiWindowSettings *settings = g.SettingsWindows.begin(); settings != NULL;
24494 settings = g.SettingsWindows.next_chunk(settings))
24495 if (settings->DockId != 0)
24496 BulletText("Window '%s' -> DockId %08X DockOrder=%d", settings->GetName(), settings->DockId,
24497 settings->DockOrder);
24498 Text("In SettingsNodes:");
24499 for (int n = 0; n < dc->NodesSettings.Size; n++)
24500 {
24501 ImGuiDockNodeSettings *settings = &dc->NodesSettings[n];
24502 const char *selected_tab_name = NULL;
24503 if (settings->SelectedTabId)
24504 {
24505 if (ImGuiWindow *window = FindWindowByID(settings->SelectedTabId))
24506 selected_tab_name = window->Name;
24507 else if (ImGuiWindowSettings *window_settings = FindWindowSettingsByID(settings->SelectedTabId))
24508 selected_tab_name = window_settings->GetName();
24509 }
24510 BulletText("Node %08X, Parent %08X, SelectedTab %08X ('%s')", settings->ID, settings->ParentNodeId,
24511 settings->SelectedTabId,
24512 selected_tab_name ? selected_tab_name
24513 : settings->SelectedTabId ? "N/A"
24514 : "");
24515 }
24516 TreePop();
24517 }
24518#endif // #ifdef IMGUI_HAS_DOCK
24519
24520 if (TreeNode("SettingsIniData", "Settings unpacked data (.ini): %d bytes", g.SettingsIniData.size()))
24521 {
24522 InputTextMultiline("##Ini", (char *)(void *)g.SettingsIniData.c_str(), g.SettingsIniData.Buf.Size,
24523 ImVec2(-FLT_MIN, GetTextLineHeight() * 20), ImGuiInputTextFlags_ReadOnly);
24524 TreePop();
24525 }
24526 TreePop();
24527 }
24528
24529 // Settings
24530 if (TreeNode("Memory allocations"))
24531 {
24532 ImGuiDebugAllocInfo *info = &g.DebugAllocInfo;
24533 Text("%d current allocations", info->TotalAllocCount - info->TotalFreeCount);
24534 if (SmallButton("GC now"))
24535 {
24536 g.GcCompactAll = true;
24537 }
24538 Text("Recent frames with allocations:");
24539 int buf_size = IM_ARRAYSIZE(info->LastEntriesBuf);
24540 for (int n = buf_size - 1; n >= 0; n--)
24541 {
24542 ImGuiDebugAllocEntry *entry = &info->LastEntriesBuf[(info->LastEntriesIdx - n + buf_size) % buf_size];
24543 BulletText("Frame %06d: %+3d ( %2d alloc, %2d free )", entry->FrameCount,
24544 entry->AllocCount - entry->FreeCount, entry->AllocCount, entry->FreeCount);
24545 if (n == 0)
24546 {
24547 SameLine();
24548 Text("<- %d frames ago", g.FrameCount - entry->FrameCount);
24549 }
24550 }
24551 TreePop();
24552 }
24553
24554 if (TreeNode("Inputs"))
24555 {
24556 Text("KEYBOARD/GAMEPAD/MOUSE KEYS");
24557 {
24558 // User code should never have to go through such hoops! You can generally iterate between
24559 // ImGuiKey_NamedKey_BEGIN and ImGuiKey_NamedKey_END.
24560 Indent();
24561 Text("Keys down:");
24562 for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1))
24563 {
24564 if (!IsKeyDown(key))
24565 continue;
24566 SameLine();
24567 Text(IsNamedKey(key) ? "\"%s\"" : "\"%s\" %d", GetKeyName(key), key);
24568 SameLine();
24569 Text("(%.02f)", GetKeyData(key)->DownDuration);
24570 }
24571 Text("Keys pressed:");
24572 for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1))
24573 {
24574 if (!IsKeyPressed(key))
24575 continue;
24576 SameLine();
24577 Text(IsNamedKey(key) ? "\"%s\"" : "\"%s\" %d", GetKeyName(key), key);
24578 }
24579 Text("Keys released:");
24580 for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1))
24581 {
24582 if (!IsKeyReleased(key))
24583 continue;
24584 SameLine();
24585 Text(IsNamedKey(key) ? "\"%s\"" : "\"%s\" %d", GetKeyName(key), key);
24586 }
24587 Text("Keys mods: %s%s%s%s", io.KeyCtrl ? "CTRL " : "", io.KeyShift ? "SHIFT " : "", io.KeyAlt ? "ALT " : "",
24588 io.KeySuper ? "SUPER " : "");
24589 Text("Chars queue:");
24590 for (int i = 0; i < io.InputQueueCharacters.Size; i++)
24591 {
24592 ImWchar c = io.InputQueueCharacters[i];
24593 SameLine();
24594 Text("\'%c\' (0x%04X)", (c > ' ' && c <= 255) ? (char)c : '?', c);
24595 } // FIXME: We should convert 'c' to UTF-8 here but the functions are not public.
24596 DebugRenderKeyboardPreview(GetWindowDrawList());
24597 Unindent();
24598 }
24599
24600 Text("MOUSE STATE");
24601 {
24602 Indent();
24603 if (IsMousePosValid())
24604 Text("Mouse pos: (%g, %g)", io.MousePos.x, io.MousePos.y);
24605 else
24606 Text("Mouse pos: <INVALID>");
24607 Text("Mouse delta: (%g, %g)", io.MouseDelta.x, io.MouseDelta.y);
24608 int count = IM_ARRAYSIZE(io.MouseDown);
24609 Text("Mouse down:");
24610 for (int i = 0; i < count; i++)
24611 if (IsMouseDown(i))
24612 {
24613 SameLine();
24614 Text("b%d (%.02f secs)", i, io.MouseDownDuration[i]);
24615 }
24616 Text("Mouse clicked:");
24617 for (int i = 0; i < count; i++)
24618 if (IsMouseClicked(i))
24619 {
24620 SameLine();
24621 Text("b%d (%d)", i, io.MouseClickedCount[i]);
24622 }
24623 Text("Mouse released:");
24624 for (int i = 0; i < count; i++)
24625 if (IsMouseReleased(i))
24626 {
24627 SameLine();
24628 Text("b%d", i);
24629 }
24630 Text("Mouse wheel: %.1f", io.MouseWheel);
24631 Text("MouseStationaryTimer: %.2f", g.MouseStationaryTimer);
24632 Text("Mouse source: %s", GetMouseSourceName(io.MouseSource));
24633 Text("Pen Pressure: %.1f", io.PenPressure); // Note: currently unused
24634 Unindent();
24635 }
24636
24637 Text("MOUSE WHEELING");
24638 {
24639 Indent();
24640 Text("WheelingWindow: '%s'", g.WheelingWindow ? g.WheelingWindow->Name : "NULL");
24641 Text("WheelingWindowReleaseTimer: %.2f", g.WheelingWindowReleaseTimer);
24642 Text("WheelingAxisAvg[] = { %.3f, %.3f }, Main Axis: %s", g.WheelingAxisAvg.x, g.WheelingAxisAvg.y,
24643 (g.WheelingAxisAvg.x > g.WheelingAxisAvg.y) ? "X"
24644 : (g.WheelingAxisAvg.x < g.WheelingAxisAvg.y) ? "Y"
24645 : "<none>");
24646 Unindent();
24647 }
24648
24649 Text("KEY OWNERS");
24650 {
24651 Indent();
24652 if (BeginChild("##owners", ImVec2(-FLT_MIN, GetTextLineHeightWithSpacing() * 8),
24653 ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY, ImGuiWindowFlags_NoSavedSettings))
24654 for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1))
24655 {
24656 ImGuiKeyOwnerData *owner_data = GetKeyOwnerData(&g, key);
24657 if (owner_data->OwnerCurr == ImGuiKeyOwner_NoOwner)
24658 continue;
24659 Text("%s: 0x%08X%s", GetKeyName(key), owner_data->OwnerCurr,
24660 owner_data->LockUntilRelease ? " LockUntilRelease"
24661 : owner_data->LockThisFrame ? " LockThisFrame"
24662 : "");
24663 DebugLocateItemOnHover(owner_data->OwnerCurr);
24664 }
24665 EndChild();
24666 Unindent();
24667 }
24668 Text("SHORTCUT ROUTING");
24669 SameLine();
24670 MetricsHelpMarker("Declared shortcut routes automatically set key owner when mods matches.");
24671 {
24672 Indent();
24673 if (BeginChild("##routes", ImVec2(-FLT_MIN, GetTextLineHeightWithSpacing() * 8),
24674 ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY, ImGuiWindowFlags_NoSavedSettings))
24675 for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1))
24676 {
24677 ImGuiKeyRoutingTable *rt = &g.KeysRoutingTable;
24678 for (ImGuiKeyRoutingIndex idx = rt->Index[key - ImGuiKey_NamedKey_BEGIN]; idx != -1;)
24679 {
24680 ImGuiKeyRoutingData *routing_data = &rt->Entries[idx];
24681 ImGuiKeyChord key_chord = key | routing_data->Mods;
24682 Text("%s: 0x%08X (scored %d)", GetKeyChordName(key_chord), routing_data->RoutingCurr,
24683 routing_data->RoutingCurrScore);
24684 DebugLocateItemOnHover(routing_data->RoutingCurr);
24685 if (g.IO.ConfigDebugIsDebuggerPresent)
24686 {
24687 SameLine();
24688 if (DebugBreakButton("**DebugBreak**", "in SetShortcutRouting() for this KeyChord"))
24689 g.DebugBreakInShortcutRouting = key_chord;
24690 }
24691 idx = routing_data->NextEntryIndex;
24692 }
24693 }
24694 EndChild();
24695 Text("(ActiveIdUsing: AllKeyboardKeys: %d, NavDirMask: 0x%X)", g.ActiveIdUsingAllKeyboardKeys,
24696 g.ActiveIdUsingNavDirMask);
24697 Unindent();
24698 }
24699 TreePop();
24700 }
24701
24702 if (TreeNode("Internal state"))
24703 {
24704 Text("WINDOWING");
24705 Indent();
24706 Text("HoveredWindow: '%s'", g.HoveredWindow ? g.HoveredWindow->Name : "NULL");
24707 Text("HoveredWindow->Root: '%s'", g.HoveredWindow ? g.HoveredWindow->RootWindowDockTree->Name : "NULL");
24708 Text("HoveredWindowUnderMovingWindow: '%s'",
24709 g.HoveredWindowUnderMovingWindow ? g.HoveredWindowUnderMovingWindow->Name : "NULL");
24710 Text("HoveredDockNode: 0x%08X", g.DebugHoveredDockNode ? g.DebugHoveredDockNode->ID : 0);
24711 Text("MovingWindow: '%s'", g.MovingWindow ? g.MovingWindow->Name : "NULL");
24712 Text("MouseViewport: 0x%08X (UserHovered 0x%08X, LastHovered 0x%08X)", g.MouseViewport->ID,
24713 g.IO.MouseHoveredViewport, g.MouseLastHoveredViewport ? g.MouseLastHoveredViewport->ID : 0);
24714 Unindent();
24715
24716 Text("ITEMS");
24717 Indent();
24718 Text("ActiveId: 0x%08X/0x%08X (%.2f sec), AllowOverlap: %d, Source: %s", g.ActiveId, g.ActiveIdPreviousFrame,
24719 g.ActiveIdTimer, g.ActiveIdAllowOverlap, GetInputSourceName(g.ActiveIdSource));
24720 DebugLocateItemOnHover(g.ActiveId);
24721 Text("ActiveIdWindow: '%s'", g.ActiveIdWindow ? g.ActiveIdWindow->Name : "NULL");
24722 Text("ActiveIdUsing: AllKeyboardKeys: %d, NavDirMask: %X", g.ActiveIdUsingAllKeyboardKeys,
24723 g.ActiveIdUsingNavDirMask);
24724 Text("HoveredId: 0x%08X (%.2f sec), AllowOverlap: %d", g.HoveredIdPreviousFrame, g.HoveredIdTimer,
24725 g.HoveredIdAllowOverlap); // Not displaying g.HoveredId as it is update mid-frame
24726 Text("HoverItemDelayId: 0x%08X, Timer: %.2f, ClearTimer: %.2f", g.HoverItemDelayId, g.HoverItemDelayTimer,
24727 g.HoverItemDelayClearTimer);
24728 Text("DragDrop: %d, SourceId = 0x%08X, Payload \"%s\" (%d bytes)", g.DragDropActive, g.DragDropPayload.SourceId,
24729 g.DragDropPayload.DataType, g.DragDropPayload.DataSize);
24730 DebugLocateItemOnHover(g.DragDropPayload.SourceId);
24731 Unindent();
24732
24733 Text("NAV,FOCUS");
24734 Indent();
24735 Text("NavWindow: '%s'", g.NavWindow ? g.NavWindow->Name : "NULL");
24736 Text("NavId: 0x%08X, NavLayer: %d", g.NavId, g.NavLayer);
24737 DebugLocateItemOnHover(g.NavId);
24738 Text("NavInputSource: %s", GetInputSourceName(g.NavInputSource));
24739 Text("NavLastValidSelectionUserData = %" IM_PRId64 " (0x%" IM_PRIX64 ")", g.NavLastValidSelectionUserData,
24740 g.NavLastValidSelectionUserData);
24741 Text("NavActive: %d, NavVisible: %d", g.IO.NavActive, g.IO.NavVisible);
24742 Text("NavActivateId/DownId/PressedId: %08X/%08X/%08X", g.NavActivateId, g.NavActivateDownId,
24743 g.NavActivatePressedId);
24744 Text("NavActivateFlags: %04X", g.NavActivateFlags);
24745 Text("NavCursorVisible: %d, NavHighlightItemUnderNav: %d", g.NavCursorVisible, g.NavHighlightItemUnderNav);
24746 Text("NavFocusScopeId = 0x%08X", g.NavFocusScopeId);
24747 Text("NavFocusRoute[] = ");
24748 for (int path_n = g.NavFocusRoute.Size - 1; path_n >= 0; path_n--)
24749 {
24750 const ImGuiFocusScopeData &focus_scope = g.NavFocusRoute[path_n];
24751 SameLine(0.0f, 0.0f);
24752 Text("0x%08X/", focus_scope.ID);
24753 SetItemTooltip("In window \"%s\"", FindWindowByID(focus_scope.WindowID)->Name);
24754 }
24755 Text("NavWindowingTarget: '%s'", g.NavWindowingTarget ? g.NavWindowingTarget->Name : "NULL");
24756 Unindent();
24757
24758 TreePop();
24759 }
24760
24761 // Overlay: Display windows Rectangles and Begin Order
24762 if (cfg->ShowWindowsRects || cfg->ShowWindowsBeginOrder)
24763 {
24764 for (ImGuiWindow *window : g.Windows)
24765 {
24766 if (!window->WasActive)
24767 continue;
24768 ImDrawList *draw_list = GetForegroundDrawList(window);
24769 if (cfg->ShowWindowsRects)
24770 {
24771 ImRect r = Funcs::GetWindowRect(window, cfg->ShowWindowsRectsType);
24772 draw_list->AddRect(r.Min, r.Max, IM_COL32(255, 0, 128, 255));
24773 }
24774 if (cfg->ShowWindowsBeginOrder && !(window->Flags & ImGuiWindowFlags_ChildWindow))
24775 {
24776 char buf[32];
24777 ImFormatString(buf, IM_ARRAYSIZE(buf), "%d", window->BeginOrderWithinContext);
24778 float font_size = GetFontSize();
24779 draw_list->AddRectFilled(window->Pos, window->Pos + ImVec2(font_size, font_size),
24780 IM_COL32(200, 100, 100, 255));
24781 draw_list->AddText(window->Pos, IM_COL32(255, 255, 255, 255), buf);
24782 }
24783 }
24784 }
24785
24786 // Overlay: Display Tables Rectangles
24787 if (cfg->ShowTablesRects)
24788 {
24789 for (int table_n = 0; table_n < g.Tables.GetMapSize(); table_n++)
24790 {
24791 ImGuiTable *table = g.Tables.TryGetMapData(table_n);
24792 if (table == NULL || table->LastFrameActive < g.FrameCount - 1)
24793 continue;
24794 ImDrawList *draw_list = GetForegroundDrawList(table->OuterWindow);
24795 if (cfg->ShowTablesRectsType >= TRT_ColumnsRect)
24796 {
24797 for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
24798 {
24799 ImRect r = Funcs::GetTableRect(table, cfg->ShowTablesRectsType, column_n);
24800 ImU32 col = (table->HoveredColumnBody == column_n) ? IM_COL32(255, 255, 128, 255)
24801 : IM_COL32(255, 0, 128, 255);
24802 float thickness = (table->HoveredColumnBody == column_n) ? 3.0f : 1.0f;
24803 draw_list->AddRect(r.Min, r.Max, col, 0.0f, 0, thickness);
24804 }
24805 }
24806 else
24807 {
24808 ImRect r = Funcs::GetTableRect(table, cfg->ShowTablesRectsType, -1);
24809 draw_list->AddRect(r.Min, r.Max, IM_COL32(255, 0, 128, 255));
24810 }
24811 }
24812 }
24813
24814#ifdef IMGUI_HAS_DOCK
24815 // Overlay: Display Docking info
24816 if (cfg->ShowDockingNodes && g.IO.KeyCtrl && g.DebugHoveredDockNode)
24817 {
24818 char buf[64] = "";
24819 char *p = buf;
24820 ImGuiDockNode *node = g.DebugHoveredDockNode;
24821 ImDrawList *overlay_draw_list =
24822 node->HostWindow ? GetForegroundDrawList(node->HostWindow) : GetForegroundDrawList(GetMainViewport());
24823 p += ImFormatString(p, buf + IM_ARRAYSIZE(buf) - p, "DockId: %X%s\n", node->ID,
24824 node->IsCentralNode() ? " *CentralNode*" : "");
24825 p += ImFormatString(p, buf + IM_ARRAYSIZE(buf) - p, "WindowClass: %08X\n", node->WindowClass.ClassId);
24826 p += ImFormatString(p, buf + IM_ARRAYSIZE(buf) - p, "Size: (%.0f, %.0f)\n", node->Size.x, node->Size.y);
24827 p +=
24828 ImFormatString(p, buf + IM_ARRAYSIZE(buf) - p, "SizeRef: (%.0f, %.0f)\n", node->SizeRef.x, node->SizeRef.y);
24829 int depth = DockNodeGetDepth(node);
24830 overlay_draw_list->AddRect(node->Pos + ImVec2(3, 3) * (float)depth,
24831 node->Pos + node->Size - ImVec2(3, 3) * (float)depth, IM_COL32(200, 100, 100, 255));
24832 ImVec2 pos = node->Pos + ImVec2(3, 3) * (float)depth;
24833 overlay_draw_list->AddRectFilled(pos - ImVec2(1, 1), pos + CalcTextSize(buf) + ImVec2(1, 1),
24834 IM_COL32(200, 100, 100, 255));
24835 overlay_draw_list->AddText(NULL, 0.0f, pos, IM_COL32(255, 255, 255, 255), buf);
24836 }
24837#endif // #ifdef IMGUI_HAS_DOCK
24838
24839 End();
24840}
24841
24842void ImGui::DebugBreakClearData()
24843{
24844 // Those fields are scattered in their respective subsystem to stay in hot-data locations
24845 ImGuiContext &g = *GImGui;
24846 g.DebugBreakInWindow = 0;
24847 g.DebugBreakInTable = 0;
24848 g.DebugBreakInShortcutRouting = ImGuiKey_None;
24849}
24850
24851void ImGui::DebugBreakButtonTooltip(bool keyboard_only, const char *description_of_location)
24852{
24853 if (!BeginItemTooltip())
24854 return;
24855 Text("To call IM_DEBUG_BREAK() %s:", description_of_location);
24856 Separator();
24857 TextUnformatted(keyboard_only ? "- Press 'Pause/Break' on keyboard."
24858 : "- Press 'Pause/Break' on keyboard.\n- or Click (may alter focus/active id).\n- or "
24859 "navigate using keyboard and press space.");
24860 Separator();
24861 TextUnformatted("Choose one way that doesn't interfere with what you are trying to debug!\nYou need a debugger "
24862 "attached or this will crash!");
24863 EndTooltip();
24864}
24865
24866// Special button that doesn't take focus, doesn't take input owner, and can be activated without a click etc.
24867// In order to reduce interferences with the contents we are trying to debug into.
24868bool ImGui::DebugBreakButton(const char *label, const char *description_of_location)
24869{
24870 ImGuiWindow *window = GetCurrentWindow();
24871 if (window->SkipItems)
24872 return false;
24873
24874 ImGuiContext &g = *GImGui;
24875 const ImGuiID id = window->GetID(label);
24876 const ImVec2 label_size = CalcTextSize(label, NULL, true);
24877 ImVec2 pos = window->DC.CursorPos + ImVec2(0.0f, window->DC.CurrLineTextBaseOffset);
24878 ImVec2 size = ImVec2(label_size.x + g.Style.FramePadding.x * 2.0f, label_size.y);
24879
24880 const ImRect bb(pos, pos + size);
24881 ItemSize(size, 0.0f);
24882 if (!ItemAdd(bb, id))
24883 return false;
24884
24885 // WE DO NOT USE ButtonEx() or ButtonBehavior() in order to reduce our side-effects.
24886 bool hovered = ItemHoverable(bb, id, g.CurrentItemFlags);
24887 bool pressed = hovered && (IsKeyChordPressed(g.DebugBreakKeyChord) || IsMouseClicked(0) || g.NavActivateId == id);
24888 DebugBreakButtonTooltip(false, description_of_location);
24889
24890 ImVec4 col4f = GetStyleColorVec4(hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
24891 ImVec4 hsv;
24892 ColorConvertRGBtoHSV(col4f.x, col4f.y, col4f.z, hsv.x, hsv.y, hsv.z);
24893 ColorConvertHSVtoRGB(hsv.x + 0.20f, hsv.y, hsv.z, col4f.x, col4f.y, col4f.z);
24894
24895 RenderNavCursor(bb, id);
24896 RenderFrame(bb.Min, bb.Max, GetColorU32(col4f), true, g.Style.FrameRounding);
24897 RenderTextClipped(bb.Min, bb.Max, label, NULL, &label_size, g.Style.ButtonTextAlign, &bb);
24898
24899 IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
24900 return pressed;
24901}
24902
24903// [DEBUG] Display contents of Columns
24904void ImGui::DebugNodeColumns(ImGuiOldColumns *columns)
24905{
24906 if (!TreeNode((void *)(uintptr_t)columns->ID, "Columns Id: 0x%08X, Count: %d, Flags: 0x%04X", columns->ID,
24907 columns->Count, columns->Flags))
24908 return;
24909 BulletText("Width: %.1f (MinX: %.1f, MaxX: %.1f)", columns->OffMaxX - columns->OffMinX, columns->OffMinX,
24910 columns->OffMaxX);
24911 for (ImGuiOldColumnData &column : columns->Columns)
24912 BulletText("Column %02d: OffsetNorm %.3f (= %.1f px)", (int)columns->Columns.index_from_ptr(&column),
24913 column.OffsetNorm, GetColumnOffsetFromNorm(columns, column.OffsetNorm));
24914 TreePop();
24915}
24916
24917static void DebugNodeDockNodeFlags(ImGuiDockNodeFlags *p_flags, const char *label, bool enabled)
24918{
24919 using namespace ImGui;
24920 PushID(label);
24921 PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f));
24922 Text("%s:", label);
24923 if (!enabled)
24924 BeginDisabled();
24925 CheckboxFlags("NoResize", p_flags, ImGuiDockNodeFlags_NoResize);
24926 CheckboxFlags("NoResizeX", p_flags, ImGuiDockNodeFlags_NoResizeX);
24927 CheckboxFlags("NoResizeY", p_flags, ImGuiDockNodeFlags_NoResizeY);
24928 CheckboxFlags("NoTabBar", p_flags, ImGuiDockNodeFlags_NoTabBar);
24929 CheckboxFlags("HiddenTabBar", p_flags, ImGuiDockNodeFlags_HiddenTabBar);
24930 CheckboxFlags("NoWindowMenuButton", p_flags, ImGuiDockNodeFlags_NoWindowMenuButton);
24931 CheckboxFlags("NoCloseButton", p_flags, ImGuiDockNodeFlags_NoCloseButton);
24932 CheckboxFlags("DockedWindowsInFocusRoute", p_flags, ImGuiDockNodeFlags_DockedWindowsInFocusRoute);
24933 CheckboxFlags("NoDocking", p_flags, ImGuiDockNodeFlags_NoDocking); // Multiple flags
24934 CheckboxFlags("NoDockingSplit", p_flags, ImGuiDockNodeFlags_NoDockingSplit);
24935 CheckboxFlags("NoDockingSplitOther", p_flags, ImGuiDockNodeFlags_NoDockingSplitOther);
24936 CheckboxFlags("NoDockingOver", p_flags, ImGuiDockNodeFlags_NoDockingOverMe);
24937 CheckboxFlags("NoDockingOverOther", p_flags, ImGuiDockNodeFlags_NoDockingOverOther);
24938 CheckboxFlags("NoDockingOverEmpty", p_flags, ImGuiDockNodeFlags_NoDockingOverEmpty);
24939 CheckboxFlags("NoUndocking", p_flags, ImGuiDockNodeFlags_NoUndocking);
24940 if (!enabled)
24941 EndDisabled();
24942 PopStyleVar();
24943 PopID();
24944}
24945
24946// [DEBUG] Display contents of ImDockNode
24947void ImGui::DebugNodeDockNode(ImGuiDockNode *node, const char *label)
24948{
24949 ImGuiContext &g = *GImGui;
24950 const bool is_alive = (g.FrameCount - node->LastFrameAlive < 2); // Submitted with ImGuiDockNodeFlags_KeepAliveOnly
24951 const bool is_active = (g.FrameCount - node->LastFrameActive < 2); // Submitted
24952 if (!is_alive)
24953 {
24954 PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled));
24955 }
24956 bool open;
24957 ImGuiTreeNodeFlags tree_node_flags = node->IsFocused ? ImGuiTreeNodeFlags_Selected : ImGuiTreeNodeFlags_None;
24958 if (node->Windows.Size > 0)
24959 open = TreeNodeEx((void *)(intptr_t)node->ID, tree_node_flags, "%s 0x%04X%s: %d windows (vis: '%s')", label,
24960 node->ID, node->IsVisible ? "" : " (hidden)", node->Windows.Size,
24961 node->VisibleWindow ? node->VisibleWindow->Name : "NULL");
24962 else
24963 open = TreeNodeEx((void *)(intptr_t)node->ID, tree_node_flags, "%s 0x%04X%s: %s (vis: '%s')", label, node->ID,
24964 node->IsVisible ? "" : " (hidden)",
24965 (node->SplitAxis == ImGuiAxis_X) ? "horizontal split"
24966 : (node->SplitAxis == ImGuiAxis_Y) ? "vertical split"
24967 : "empty",
24968 node->VisibleWindow ? node->VisibleWindow->Name : "NULL");
24969 if (!is_alive)
24970 {
24971 PopStyleColor();
24972 }
24973 if (is_active && IsItemHovered())
24974 if (ImGuiWindow *window = node->HostWindow ? node->HostWindow : node->VisibleWindow)
24975 GetForegroundDrawList(window)->AddRect(node->Pos, node->Pos + node->Size, IM_COL32(255, 255, 0, 255));
24976 if (open)
24977 {
24978 IM_ASSERT(node->ChildNodes[0] == NULL || node->ChildNodes[0]->ParentNode == node);
24979 IM_ASSERT(node->ChildNodes[1] == NULL || node->ChildNodes[1]->ParentNode == node);
24980 BulletText("Pos (%.0f,%.0f), Size (%.0f, %.0f) Ref (%.0f, %.0f)", node->Pos.x, node->Pos.y, node->Size.x,
24981 node->Size.y, node->SizeRef.x, node->SizeRef.y);
24982 DebugNodeWindow(node->HostWindow, "HostWindow");
24983 DebugNodeWindow(node->VisibleWindow, "VisibleWindow");
24984 BulletText("SelectedTabID: 0x%08X, LastFocusedNodeID: 0x%08X", node->SelectedTabId, node->LastFocusedNodeId);
24985 BulletText("Misc:%s%s%s%s%s%s%s", node->IsDockSpace() ? " IsDockSpace" : "",
24986 node->IsCentralNode() ? " IsCentralNode" : "", is_alive ? " IsAlive" : "",
24987 is_active ? " IsActive" : "", node->IsFocused ? " IsFocused" : "",
24988 node->WantLockSizeOnce ? " WantLockSizeOnce" : "",
24989 node->HasCentralNodeChild ? " HasCentralNodeChild" : "");
24990 if (TreeNode("flags", "Flags Merged: 0x%04X, Local: 0x%04X, InWindows: 0x%04X, Shared: 0x%04X",
24991 node->MergedFlags, node->LocalFlags, node->LocalFlagsInWindows, node->SharedFlags))
24992 {
24993 if (BeginTable("flags", 4))
24994 {
24995 TableNextColumn();
24996 DebugNodeDockNodeFlags(&node->MergedFlags, "MergedFlags", false);
24997 TableNextColumn();
24998 DebugNodeDockNodeFlags(&node->LocalFlags, "LocalFlags", true);
24999 TableNextColumn();
25000 DebugNodeDockNodeFlags(&node->LocalFlagsInWindows, "LocalFlagsInWindows", false);
25001 TableNextColumn();
25002 DebugNodeDockNodeFlags(&node->SharedFlags, "SharedFlags", true);
25003 EndTable();
25004 }
25005 TreePop();
25006 }
25007 if (node->ParentNode)
25008 DebugNodeDockNode(node->ParentNode, "ParentNode");
25009 if (node->ChildNodes[0])
25010 DebugNodeDockNode(node->ChildNodes[0], "Child[0]");
25011 if (node->ChildNodes[1])
25012 DebugNodeDockNode(node->ChildNodes[1], "Child[1]");
25013 if (node->TabBar)
25014 DebugNodeTabBar(node->TabBar, "TabBar");
25015 DebugNodeWindowsList(&node->Windows, "Windows");
25016
25017 TreePop();
25018 }
25019}
25020
25021// [DEBUG] Display contents of ImDrawList
25022// Note that both 'window' and 'viewport' may be NULL here. Viewport is generally null of destroyed popups which
25023// previously owned a viewport.
25024void ImGui::DebugNodeDrawList(ImGuiWindow *window, ImGuiViewportP *viewport, const ImDrawList *draw_list,
25025 const char *label)
25026{
25027 ImGuiContext &g = *GImGui;
25028 ImGuiMetricsConfig *cfg = &g.DebugMetricsConfig;
25029 int cmd_count = draw_list->CmdBuffer.Size;
25030 if (cmd_count > 0 && draw_list->CmdBuffer.back().ElemCount == 0 && draw_list->CmdBuffer.back().UserCallback == NULL)
25031 cmd_count--;
25032 bool node_open = TreeNode(draw_list, "%s: '%s' %d vtx, %d indices, %d cmds", label,
25033 draw_list->_OwnerName ? draw_list->_OwnerName : "", draw_list->VtxBuffer.Size,
25034 draw_list->IdxBuffer.Size, cmd_count);
25035 if (draw_list == GetWindowDrawList())
25036 {
25037 SameLine();
25038 TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), "CURRENTLY APPENDING"); // Can't display stats for active draw list!
25039 // (we don't have the data double-buffered)
25040 if (node_open)
25041 TreePop();
25042 return;
25043 }
25044
25045 ImDrawList *fg_draw_list =
25046 viewport ? GetForegroundDrawList(viewport) : NULL; // Render additional visuals into the top-most draw list
25047 if (window && IsItemHovered() && fg_draw_list)
25048 fg_draw_list->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 255, 0, 255));
25049 if (!node_open)
25050 return;
25051
25052 if (window && !window->WasActive)
25053 TextDisabled("Warning: owning Window is inactive. This DrawList is not being rendered!");
25054
25055 for (const ImDrawCmd *pcmd = draw_list->CmdBuffer.Data; pcmd < draw_list->CmdBuffer.Data + cmd_count; pcmd++)
25056 {
25057 if (pcmd->UserCallback)
25058 {
25059 BulletText("Callback %p, user_data %p", pcmd->UserCallback, pcmd->UserCallbackData);
25060 continue;
25061 }
25062
25063 char texid_desc[20];
25064 FormatTextureIDForDebugDisplay(texid_desc, IM_ARRAYSIZE(texid_desc), pcmd->TextureId);
25065 char buf[300];
25066 ImFormatString(buf, IM_ARRAYSIZE(buf), "DrawCmd:%5d tris, Tex %s, ClipRect (%4.0f,%4.0f)-(%4.0f,%4.0f)",
25067 pcmd->ElemCount / 3, texid_desc, pcmd->ClipRect.x, pcmd->ClipRect.y, pcmd->ClipRect.z,
25068 pcmd->ClipRect.w);
25069 bool pcmd_node_open = TreeNode((void *)(pcmd - draw_list->CmdBuffer.begin()), "%s", buf);
25070 if (IsItemHovered() && (cfg->ShowDrawCmdMesh || cfg->ShowDrawCmdBoundingBoxes) && fg_draw_list)
25071 DebugNodeDrawCmdShowMeshAndBoundingBox(fg_draw_list, draw_list, pcmd, cfg->ShowDrawCmdMesh,
25072 cfg->ShowDrawCmdBoundingBoxes);
25073 if (!pcmd_node_open)
25074 continue;
25075
25076 // Calculate approximate coverage area (touched pixel count)
25077 // This will be in pixels squared as long there's no post-scaling happening to the renderer output.
25078 const ImDrawIdx *idx_buffer = (draw_list->IdxBuffer.Size > 0) ? draw_list->IdxBuffer.Data : NULL;
25079 const ImDrawVert *vtx_buffer = draw_list->VtxBuffer.Data + pcmd->VtxOffset;
25080 float total_area = 0.0f;
25081 for (unsigned int idx_n = pcmd->IdxOffset; idx_n < pcmd->IdxOffset + pcmd->ElemCount;)
25082 {
25083 ImVec2 triangle[3];
25084 for (int n = 0; n < 3; n++, idx_n++)
25085 triangle[n] = vtx_buffer[idx_buffer ? idx_buffer[idx_n] : idx_n].pos;
25086 total_area += ImTriangleArea(triangle[0], triangle[1], triangle[2]);
25087 }
25088
25089 // Display vertex information summary. Hover to get all triangles drawn in wire-frame
25090 ImFormatString(buf, IM_ARRAYSIZE(buf), "Mesh: ElemCount: %d, VtxOffset: +%d, IdxOffset: +%d, Area: ~%0.f px",
25091 pcmd->ElemCount, pcmd->VtxOffset, pcmd->IdxOffset, total_area);
25092 Selectable(buf);
25093 if (IsItemHovered() && fg_draw_list)
25094 DebugNodeDrawCmdShowMeshAndBoundingBox(fg_draw_list, draw_list, pcmd, true, false);
25095
25096 // Display individual triangles/vertices. Hover on to get the corresponding triangle highlighted.
25097 ImGuiListClipper clipper;
25098 clipper.Begin(pcmd->ElemCount / 3); // Manually coarse clip our print out of individual vertices to save CPU,
25099 // only items that may be visible.
25100 while (clipper.Step())
25101 for (int prim = clipper.DisplayStart, idx_i = pcmd->IdxOffset + clipper.DisplayStart * 3;
25102 prim < clipper.DisplayEnd; prim++)
25103 {
25104 char *buf_p = buf, *buf_end = buf + IM_ARRAYSIZE(buf);
25105 ImVec2 triangle[3];
25106 for (int n = 0; n < 3; n++, idx_i++)
25107 {
25108 const ImDrawVert &v = vtx_buffer[idx_buffer ? idx_buffer[idx_i] : idx_i];
25109 triangle[n] = v.pos;
25110 buf_p +=
25111 ImFormatString(buf_p, buf_end - buf_p, "%s %04d: pos (%8.2f,%8.2f), uv (%.6f,%.6f), col %08X\n",
25112 (n == 0) ? "Vert:" : " ", idx_i, v.pos.x, v.pos.y, v.uv.x, v.uv.y, v.col);
25113 }
25114
25115 Selectable(buf, false);
25116 if (fg_draw_list && IsItemHovered())
25117 {
25118 ImDrawListFlags backup_flags = fg_draw_list->Flags;
25119 fg_draw_list->Flags &=
25120 ~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines is more readable for very
25121 // large and thin triangles.
25122 fg_draw_list->AddPolyline(triangle, 3, IM_COL32(255, 255, 0, 255), ImDrawFlags_Closed, 1.0f);
25123 fg_draw_list->Flags = backup_flags;
25124 }
25125 }
25126 TreePop();
25127 }
25128 TreePop();
25129}
25130
25131// [DEBUG] Display mesh/aabb of a ImDrawCmd
25132void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList *out_draw_list, const ImDrawList *draw_list,
25133 const ImDrawCmd *draw_cmd, bool show_mesh, bool show_aabb)
25134{
25135 IM_ASSERT(show_mesh || show_aabb);
25136
25137 // Draw wire-frame version of all triangles
25138 ImRect clip_rect = draw_cmd->ClipRect;
25139 ImRect vtxs_rect(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX);
25140 ImDrawListFlags backup_flags = out_draw_list->Flags;
25141 out_draw_list->Flags &= ~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines is more readable for
25142 // very large and thin triangles.
25143 for (unsigned int idx_n = draw_cmd->IdxOffset, idx_end = draw_cmd->IdxOffset + draw_cmd->ElemCount;
25144 idx_n < idx_end;)
25145 {
25146 ImDrawIdx *idx_buffer = (draw_list->IdxBuffer.Size > 0)
25147 ? draw_list->IdxBuffer.Data
25148 : NULL; // We don't hold on those pointers past iterations as ->AddPolyline() may
25149 // invalidate them if out_draw_list==draw_list
25150 ImDrawVert *vtx_buffer = draw_list->VtxBuffer.Data + draw_cmd->VtxOffset;
25151
25152 ImVec2 triangle[3];
25153 for (int n = 0; n < 3; n++, idx_n++)
25154 vtxs_rect.Add((triangle[n] = vtx_buffer[idx_buffer ? idx_buffer[idx_n] : idx_n].pos));
25155 if (show_mesh)
25156 out_draw_list->AddPolyline(triangle, 3, IM_COL32(255, 255, 0, 255), ImDrawFlags_Closed,
25157 1.0f); // In yellow: mesh triangles
25158 }
25159 // Draw bounding boxes
25160 if (show_aabb)
25161 {
25162 out_draw_list->AddRect(ImTrunc(clip_rect.Min), ImTrunc(clip_rect.Max),
25163 IM_COL32(255, 0, 255, 255)); // In pink: clipping rectangle submitted to GPU
25164 out_draw_list->AddRect(ImTrunc(vtxs_rect.Min), ImTrunc(vtxs_rect.Max),
25165 IM_COL32(0, 255, 255, 255)); // In cyan: bounding box of triangles
25166 }
25167 out_draw_list->Flags = backup_flags;
25168}
25169
25170// [DEBUG] Display details for a single font, called by ShowStyleEditor().
25171void ImGui::DebugNodeFont(ImFont *font)
25172{
25173 bool opened =
25174 TreeNode(font, "Font: \"%s\": %.2f px, %d glyphs, %d sources(s)", font->Sources ? font->Sources[0].Name : "",
25175 font->FontSize, font->Glyphs.Size, font->SourcesCount);
25176
25177 // Display preview text
25178 if (!opened)
25179 Indent();
25180 Indent();
25181 PushFont(font);
25182 Text("The quick brown fox jumps over the lazy dog");
25183 PopFont();
25184 if (!opened)
25185 {
25186 Unindent();
25187 Unindent();
25188 return;
25189 }
25190 if (SmallButton("Set as default"))
25191 GetIO().FontDefault = font;
25192
25193 // Display details
25194 SetNextItemWidth(GetFontSize() * 8);
25195 DragFloat("Font scale", &font->Scale, 0.005f, 0.3f, 2.0f, "%.1f");
25196 SameLine();
25197 MetricsHelpMarker(
25198 "Note that the default embedded font is NOT meant to be scaled.\n\n"
25199 "Font are currently rendered into bitmaps at a given size at the time of building the atlas. "
25200 "You may oversample them to get some flexibility with scaling. "
25201 "You can also render at multiple sizes and select which one to use at runtime.\n\n"
25202 "(Glimmer of hope: the atlas system will be rewritten in the future to make scaling more flexible.)");
25203 Text("Ascent: %f, Descent: %f, Height: %f", font->Ascent, font->Descent, font->Ascent - font->Descent);
25204 char c_str[5];
25205 Text("Fallback character: '%s' (U+%04X)", ImTextCharToUtf8(c_str, font->FallbackChar), font->FallbackChar);
25206 Text("Ellipsis character: '%s' (U+%04X)", ImTextCharToUtf8(c_str, font->EllipsisChar), font->EllipsisChar);
25207 const int surface_sqrt = (int)ImSqrt((float)font->MetricsTotalSurface);
25208 Text("Texture Area: about %d px ~%dx%d px", font->MetricsTotalSurface, surface_sqrt, surface_sqrt);
25209 for (int config_i = 0; config_i < font->SourcesCount; config_i++)
25210 if (font->Sources)
25211 {
25212 const ImFontConfig *src = &font->Sources[config_i];
25213 int oversample_h, oversample_v;
25214 ImFontAtlasBuildGetOversampleFactors(src, &oversample_h, &oversample_v);
25215 BulletText("Input %d: \'%s\', Oversample: (%d=>%d,%d=>%d), PixelSnapH: %d, Offset: (%.1f,%.1f)", config_i,
25216 src->Name, src->OversampleH, oversample_h, src->OversampleV, oversample_v, src->PixelSnapH,
25217 src->GlyphOffset.x, src->GlyphOffset.y);
25218 }
25219
25220 // Display all glyphs of the fonts in separate pages of 256 characters
25221 {
25222 if (TreeNode("Glyphs", "Glyphs (%d)", font->Glyphs.Size))
25223 {
25224 ImDrawList *draw_list = GetWindowDrawList();
25225 const ImU32 glyph_col = GetColorU32(ImGuiCol_Text);
25226 const float cell_size = font->FontSize * 1;
25227 const float cell_spacing = GetStyle().ItemSpacing.y;
25228 for (unsigned int base = 0; base <= IM_UNICODE_CODEPOINT_MAX; base += 256)
25229 {
25230 // Skip ahead if a large bunch of glyphs are not present in the font (test in chunks of 4k)
25231 // This is only a small optimization to reduce the number of iterations when IM_UNICODE_MAX_CODEPOINT
25232 // is large // (if ImWchar==ImWchar32 we will do at least about 272 queries here)
25233 if (!(base & 8191) && font->IsGlyphRangeUnused(base, base + 8191))
25234 {
25235 base += 8192 - 256;
25236 continue;
25237 }
25238
25239 int count = 0;
25240 for (unsigned int n = 0; n < 256; n++)
25241 if (font->FindGlyphNoFallback((ImWchar)(base + n)))
25242 count++;
25243 if (count <= 0)
25244 continue;
25245 if (!TreeNode((void *)(intptr_t)base, "U+%04X..U+%04X (%d %s)", base, base + 255, count,
25246 count > 1 ? "glyphs" : "glyph"))
25247 continue;
25248
25249 // Draw a 16x16 grid of glyphs
25250 ImVec2 base_pos = GetCursorScreenPos();
25251 for (unsigned int n = 0; n < 256; n++)
25252 {
25253 // We use ImFont::RenderChar as a shortcut because we don't have UTF-8 conversion functions
25254 // available here and thus cannot easily generate a zero-terminated UTF-8 encoded string.
25255 ImVec2 cell_p1(base_pos.x + (n % 16) * (cell_size + cell_spacing),
25256 base_pos.y + (n / 16) * (cell_size + cell_spacing));
25257 ImVec2 cell_p2(cell_p1.x + cell_size, cell_p1.y + cell_size);
25258 const ImFontGlyph *glyph = font->FindGlyphNoFallback((ImWchar)(base + n));
25259 draw_list->AddRect(cell_p1, cell_p2,
25260 glyph ? IM_COL32(255, 255, 255, 100) : IM_COL32(255, 255, 255, 50));
25261 if (!glyph)
25262 continue;
25263 font->RenderChar(draw_list, cell_size, cell_p1, glyph_col, (ImWchar)(base + n));
25264 if (IsMouseHoveringRect(cell_p1, cell_p2) && BeginTooltip())
25265 {
25266 DebugNodeFontGlyph(font, glyph);
25267 EndTooltip();
25268 }
25269 }
25270 Dummy(ImVec2((cell_size + cell_spacing) * 16, (cell_size + cell_spacing) * 16));
25271 TreePop();
25272 }
25273 TreePop();
25274 }
25275 }
25276 TreePop();
25277 Unindent();
25278}
25279
25280void ImGui::DebugNodeFontGlyph(ImFont *, const ImFontGlyph *glyph)
25281{
25282 Text("Codepoint: U+%04X", glyph->Codepoint);
25283 Separator();
25284 Text("Visible: %d", glyph->Visible);
25285 Text("AdvanceX: %.1f", glyph->AdvanceX);
25286 Text("Pos: (%.2f,%.2f)->(%.2f,%.2f)", glyph->X0, glyph->Y0, glyph->X1, glyph->Y1);
25287 Text("UV: (%.3f,%.3f)->(%.3f,%.3f)", glyph->U0, glyph->V0, glyph->U1, glyph->V1);
25288}
25289
25290// [DEBUG] Display contents of ImGuiStorage
25291void ImGui::DebugNodeStorage(ImGuiStorage *storage, const char *label)
25292{
25293 if (!TreeNode(label, "%s: %d entries, %d bytes", label, storage->Data.Size, storage->Data.size_in_bytes()))
25294 return;
25295 for (const ImGuiStoragePair &p : storage->Data)
25296 {
25297 BulletText("Key 0x%08X Value { i: %d }", p.key,
25298 p.val_i); // Important: we currently don't store a type, real value may not be integer.
25299 DebugLocateItemOnHover(p.key);
25300 }
25301 TreePop();
25302}
25303
25304// [DEBUG] Display contents of ImGuiTabBar
25305void ImGui::DebugNodeTabBar(ImGuiTabBar *tab_bar, const char *label)
25306{
25307 // Standalone tab bars (not associated to docking/windows functionality) currently hold no discernible strings.
25308 char buf[256];
25309 char *p = buf;
25310 const char *buf_end = buf + IM_ARRAYSIZE(buf);
25311 const bool is_active = (tab_bar->PrevFrameVisible >= GetFrameCount() - 2);
25312 p += ImFormatString(p, buf_end - p, "%s 0x%08X (%d tabs)%s {", label, tab_bar->ID, tab_bar->Tabs.Size,
25313 is_active ? "" : " *Inactive*");
25314 for (int tab_n = 0; tab_n < ImMin(tab_bar->Tabs.Size, 3); tab_n++)
25315 {
25316 ImGuiTabItem *tab = &tab_bar->Tabs[tab_n];
25317 p += ImFormatString(p, buf_end - p, "%s'%s'", tab_n > 0 ? ", " : "", TabBarGetTabName(tab_bar, tab));
25318 }
25319 p += ImFormatString(p, buf_end - p, (tab_bar->Tabs.Size > 3) ? " ... }" : " } ");
25320 if (!is_active)
25321 {
25322 PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled));
25323 }
25324 bool open = TreeNode(label, "%s", buf);
25325 if (!is_active)
25326 {
25327 PopStyleColor();
25328 }
25329 if (is_active && IsItemHovered())
25330 {
25331 ImDrawList *draw_list = GetForegroundDrawList();
25332 draw_list->AddRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max, IM_COL32(255, 255, 0, 255));
25333 draw_list->AddLine(ImVec2(tab_bar->ScrollingRectMinX, tab_bar->BarRect.Min.y),
25334 ImVec2(tab_bar->ScrollingRectMinX, tab_bar->BarRect.Max.y), IM_COL32(0, 255, 0, 255));
25335 draw_list->AddLine(ImVec2(tab_bar->ScrollingRectMaxX, tab_bar->BarRect.Min.y),
25336 ImVec2(tab_bar->ScrollingRectMaxX, tab_bar->BarRect.Max.y), IM_COL32(0, 255, 0, 255));
25337 }
25338 if (open)
25339 {
25340 for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
25341 {
25342 ImGuiTabItem *tab = &tab_bar->Tabs[tab_n];
25343 PushID(tab);
25344 if (SmallButton("<"))
25345 {
25346 TabBarQueueReorder(tab_bar, tab, -1);
25347 }
25348 SameLine(0, 2);
25349 if (SmallButton(">"))
25350 {
25351 TabBarQueueReorder(tab_bar, tab, +1);
25352 }
25353 SameLine();
25354 Text("%02d%c Tab 0x%08X '%s' Offset: %.2f, Width: %.2f/%.2f", tab_n,
25355 (tab->ID == tab_bar->SelectedTabId) ? '*' : ' ', tab->ID, TabBarGetTabName(tab_bar, tab), tab->Offset,
25356 tab->Width, tab->ContentWidth);
25357 PopID();
25358 }
25359 TreePop();
25360 }
25361}
25362
25363void ImGui::DebugNodeViewport(ImGuiViewportP *viewport)
25364{
25365 ImGuiContext &g = *GImGui;
25366 SetNextItemOpen(true, ImGuiCond_Once);
25367 bool open = TreeNode((void *)(intptr_t)viewport->ID, "Viewport #%d, ID: 0x%08X, Parent: 0x%08X, Window: \"%s\"",
25368 viewport->Idx, viewport->ID, viewport->ParentViewportId,
25369 viewport->Window ? viewport->Window->Name : "N/A");
25370 if (IsItemHovered())
25371 g.DebugMetricsConfig.HighlightViewportID = viewport->ID;
25372 if (open)
25373 {
25374 ImGuiWindowFlags flags = viewport->Flags;
25375 BulletText("Main Pos: (%.0f,%.0f), Size: (%.0f,%.0f)\nWorkArea Inset Left: %.0f Top: %.0f, Right: %.0f, "
25376 "Bottom: %.0f\nMonitor: %d, DpiScale: %.0f%%",
25377 viewport->Pos.x, viewport->Pos.y, viewport->Size.x, viewport->Size.y, viewport->WorkInsetMin.x,
25378 viewport->WorkInsetMin.y, viewport->WorkInsetMax.x, viewport->WorkInsetMax.y,
25379 viewport->PlatformMonitor, viewport->DpiScale * 100.0f);
25380 if (viewport->Idx > 0)
25381 {
25382 SameLine();
25383 if (SmallButton("Reset Pos"))
25384 {
25385 viewport->Pos = ImVec2(200, 200);
25386 viewport->UpdateWorkRect();
25387 if (viewport->Window)
25388 viewport->Window->Pos = viewport->Pos;
25389 }
25390 }
25391 BulletText("Flags: 0x%04X =%s%s%s%s%s%s%s%s%s%s%s%s%s", viewport->Flags,
25392 //(flags & ImGuiViewportFlags_IsPlatformWindow) ? " IsPlatformWindow" : "", // Omitting because it is
25393 //the standard
25394 (flags & ImGuiViewportFlags_IsPlatformMonitor) ? " IsPlatformMonitor" : "",
25395 (flags & ImGuiViewportFlags_IsMinimized) ? " IsMinimized" : "",
25396 (flags & ImGuiViewportFlags_IsFocused) ? " IsFocused" : "",
25397 (flags & ImGuiViewportFlags_OwnedByApp) ? " OwnedByApp" : "",
25398 (flags & ImGuiViewportFlags_NoDecoration) ? " NoDecoration" : "",
25399 (flags & ImGuiViewportFlags_NoTaskBarIcon) ? " NoTaskBarIcon" : "",
25400 (flags & ImGuiViewportFlags_NoFocusOnAppearing) ? " NoFocusOnAppearing" : "",
25401 (flags & ImGuiViewportFlags_NoFocusOnClick) ? " NoFocusOnClick" : "",
25402 (flags & ImGuiViewportFlags_NoInputs) ? " NoInputs" : "",
25403 (flags & ImGuiViewportFlags_NoRendererClear) ? " NoRendererClear" : "",
25404 (flags & ImGuiViewportFlags_NoAutoMerge) ? " NoAutoMerge" : "",
25405 (flags & ImGuiViewportFlags_TopMost) ? " TopMost" : "",
25406 (flags & ImGuiViewportFlags_CanHostOtherWindows) ? " CanHostOtherWindows" : "");
25407 for (ImDrawList *draw_list : viewport->DrawDataP.CmdLists)
25408 DebugNodeDrawList(NULL, viewport, draw_list, "DrawList");
25409 TreePop();
25410 }
25411}
25412
25413void ImGui::DebugNodePlatformMonitor(ImGuiPlatformMonitor *monitor, const char *label, int idx)
25414{
25415 BulletText("%s %d: DPI %.0f%%\n MainMin (%.0f,%.0f), MainMax (%.0f,%.0f), MainSize (%.0f,%.0f)\n WorkMin "
25416 "(%.0f,%.0f), WorkMax (%.0f,%.0f), WorkSize (%.0f,%.0f)",
25417 label, idx, monitor->DpiScale * 100.0f, monitor->MainPos.x, monitor->MainPos.y,
25418 monitor->MainPos.x + monitor->MainSize.x, monitor->MainPos.y + monitor->MainSize.y, monitor->MainSize.x,
25419 monitor->MainSize.y, monitor->WorkPos.x, monitor->WorkPos.y, monitor->WorkPos.x + monitor->WorkSize.x,
25420 monitor->WorkPos.y + monitor->WorkSize.y, monitor->WorkSize.x, monitor->WorkSize.y);
25421}
25422
25423void ImGui::DebugNodeWindow(ImGuiWindow *window, const char *label)
25424{
25425 if (window == NULL)
25426 {
25427 BulletText("%s: NULL", label);
25428 return;
25429 }
25430
25431 ImGuiContext &g = *GImGui;
25432 const bool is_active = window->WasActive;
25433 ImGuiTreeNodeFlags tree_node_flags =
25434 (window == g.NavWindow) ? ImGuiTreeNodeFlags_Selected : ImGuiTreeNodeFlags_None;
25435 if (!is_active)
25436 {
25437 PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled));
25438 }
25439 const bool open =
25440 TreeNodeEx(label, tree_node_flags, "%s '%s'%s", label, window->Name, is_active ? "" : " *Inactive*");
25441 if (!is_active)
25442 {
25443 PopStyleColor();
25444 }
25445 if (IsItemHovered() && is_active)
25446 GetForegroundDrawList(window)->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 255, 0, 255));
25447 if (!open)
25448 return;
25449
25450 if (window->MemoryCompacted)
25451 TextDisabled("Note: some memory buffers have been compacted/freed.");
25452
25453 if (g.IO.ConfigDebugIsDebuggerPresent && DebugBreakButton("**DebugBreak**", "in Begin()"))
25454 g.DebugBreakInWindow = window->ID;
25455
25456 ImGuiWindowFlags flags = window->Flags;
25457 DebugNodeDrawList(window, window->Viewport, window->DrawList, "DrawList");
25458 BulletText("Pos: (%.1f,%.1f), Size: (%.1f,%.1f), ContentSize (%.1f,%.1f) Ideal (%.1f,%.1f)", window->Pos.x,
25459 window->Pos.y, window->Size.x, window->Size.y, window->ContentSize.x, window->ContentSize.y,
25460 window->ContentSizeIdeal.x, window->ContentSizeIdeal.y);
25461 BulletText("Flags: 0x%08X (%s%s%s%s%s%s%s%s%s..)", flags, (flags & ImGuiWindowFlags_ChildWindow) ? "Child " : "",
25462 (flags & ImGuiWindowFlags_Tooltip) ? "Tooltip " : "", (flags & ImGuiWindowFlags_Popup) ? "Popup " : "",
25463 (flags & ImGuiWindowFlags_Modal) ? "Modal " : "",
25464 (flags & ImGuiWindowFlags_ChildMenu) ? "ChildMenu " : "",
25465 (flags & ImGuiWindowFlags_NoSavedSettings) ? "NoSavedSettings " : "",
25466 (flags & ImGuiWindowFlags_NoMouseInputs) ? "NoMouseInputs" : "",
25467 (flags & ImGuiWindowFlags_NoNavInputs) ? "NoNavInputs" : "",
25468 (flags & ImGuiWindowFlags_AlwaysAutoResize) ? "AlwaysAutoResize" : "");
25469 if (flags & ImGuiWindowFlags_ChildWindow)
25470 BulletText("ChildFlags: 0x%08X (%s%s%s%s..)", window->ChildFlags,
25471 (window->ChildFlags & ImGuiChildFlags_Borders) ? "Borders " : "",
25472 (window->ChildFlags & ImGuiChildFlags_ResizeX) ? "ResizeX " : "",
25473 (window->ChildFlags & ImGuiChildFlags_ResizeY) ? "ResizeY " : "",
25474 (window->ChildFlags & ImGuiChildFlags_NavFlattened) ? "NavFlattened " : "");
25475 BulletText("WindowClassId: 0x%08X", window->WindowClass.ClassId);
25476 BulletText("Scroll: (%.2f/%.2f,%.2f/%.2f) Scrollbar:%s%s", window->Scroll.x, window->ScrollMax.x, window->Scroll.y,
25477 window->ScrollMax.y, window->ScrollbarX ? "X" : "", window->ScrollbarY ? "Y" : "");
25478 BulletText("Active: %d/%d, WriteAccessed: %d, BeginOrderWithinContext: %d", window->Active, window->WasActive,
25479 window->WriteAccessed, (window->Active || window->WasActive) ? window->BeginOrderWithinContext : -1);
25480 BulletText("Appearing: %d, Hidden: %d (CanSkip %d Cannot %d), SkipItems: %d", window->Appearing, window->Hidden,
25481 window->HiddenFramesCanSkipItems, window->HiddenFramesCannotSkipItems, window->SkipItems);
25482 for (int layer = 0; layer < ImGuiNavLayer_COUNT; layer++)
25483 {
25484 ImRect r = window->NavRectRel[layer];
25485 if (r.Min.x >= r.Max.x && r.Min.y >= r.Max.y)
25486 BulletText("NavLastIds[%d]: 0x%08X", layer, window->NavLastIds[layer]);
25487 else
25488 BulletText("NavLastIds[%d]: 0x%08X at +(%.1f,%.1f)(%.1f,%.1f)", layer, window->NavLastIds[layer], r.Min.x,
25489 r.Min.y, r.Max.x, r.Max.y);
25490 DebugLocateItemOnHover(window->NavLastIds[layer]);
25491 }
25492 const ImVec2 *pr = window->NavPreferredScoringPosRel;
25493 for (int layer = 0; layer < ImGuiNavLayer_COUNT; layer++)
25494 BulletText("NavPreferredScoringPosRel[%d] = {%.1f,%.1f)", layer,
25495 (pr[layer].x == FLT_MAX ? -99999.0f : pr[layer].x),
25496 (pr[layer].y == FLT_MAX ? -99999.0f : pr[layer].y)); // Display as 99999.0f so it looks neater.
25497 BulletText("NavLayersActiveMask: %X, NavLastChildNavWindow: %s", window->DC.NavLayersActiveMask,
25498 window->NavLastChildNavWindow ? window->NavLastChildNavWindow->Name : "NULL");
25499
25500 BulletText("Viewport: %d%s, ViewportId: 0x%08X, ViewportPos: (%.1f,%.1f)",
25501 window->Viewport ? window->Viewport->Idx : -1, window->ViewportOwned ? " (Owned)" : "",
25502 window->ViewportId, window->ViewportPos.x, window->ViewportPos.y);
25503 BulletText("ViewportMonitor: %d", window->Viewport ? window->Viewport->PlatformMonitor : -1);
25504 BulletText("DockId: 0x%04X, DockOrder: %d, Act: %d, Vis: %d", window->DockId, window->DockOrder,
25505 window->DockIsActive, window->DockTabIsVisible);
25506 if (window->DockNode || window->DockNodeAsHost)
25507 DebugNodeDockNode(window->DockNodeAsHost ? window->DockNodeAsHost : window->DockNode,
25508 window->DockNodeAsHost ? "DockNodeAsHost" : "DockNode");
25509
25510 if (window->RootWindow != window)
25511 {
25512 DebugNodeWindow(window->RootWindow, "RootWindow");
25513 }
25514 if (window->RootWindowDockTree != window->RootWindow)
25515 {
25516 DebugNodeWindow(window->RootWindowDockTree, "RootWindowDockTree");
25517 }
25518 if (window->ParentWindow != NULL)
25519 {
25520 DebugNodeWindow(window->ParentWindow, "ParentWindow");
25521 }
25522 if (window->ParentWindowForFocusRoute != NULL)
25523 {
25524 DebugNodeWindow(window->ParentWindowForFocusRoute, "ParentWindowForFocusRoute");
25525 }
25526 if (window->DC.ChildWindows.Size > 0)
25527 {
25528 DebugNodeWindowsList(&window->DC.ChildWindows, "ChildWindows");
25529 }
25530 if (window->ColumnsStorage.Size > 0 && TreeNode("Columns", "Columns sets (%d)", window->ColumnsStorage.Size))
25531 {
25532 for (ImGuiOldColumns &columns : window->ColumnsStorage)
25533 DebugNodeColumns(&columns);
25534 TreePop();
25535 }
25536 DebugNodeStorage(&window->StateStorage, "Storage");
25537 TreePop();
25538}
25539
25540void ImGui::DebugNodeWindowSettings(ImGuiWindowSettings *settings)
25541{
25542 if (settings->WantDelete)
25543 BeginDisabled();
25544 Text("0x%08X \"%s\" Pos (%d,%d) Size (%d,%d) Collapsed=%d", settings->ID, settings->GetName(), settings->Pos.x,
25545 settings->Pos.y, settings->Size.x, settings->Size.y, settings->Collapsed);
25546 if (settings->WantDelete)
25547 EndDisabled();
25548}
25549
25550void ImGui::DebugNodeWindowsList(ImVector<ImGuiWindow *> *windows, const char *label)
25551{
25552 if (!TreeNode(label, "%s (%d)", label, windows->Size))
25553 return;
25554 for (int i = windows->Size - 1; i >= 0; i--) // Iterate front to back
25555 {
25556 PushID((*windows)[i]);
25557 DebugNodeWindow((*windows)[i], "Window");
25558 PopID();
25559 }
25560 TreePop();
25561}
25562
25563// FIXME-OPT: This is technically suboptimal, but it is simpler this way.
25564void ImGui::DebugNodeWindowsListByBeginStackParent(ImGuiWindow **windows, int windows_size,
25565 ImGuiWindow *parent_in_begin_stack)
25566{
25567 for (int i = 0; i < windows_size; i++)
25568 {
25569 ImGuiWindow *window = windows[i];
25570 if (window->ParentWindowInBeginStack != parent_in_begin_stack)
25571 continue;
25572 char buf[20];
25573 ImFormatString(buf, IM_ARRAYSIZE(buf), "[%04d] Window", window->BeginOrderWithinContext);
25574 // BulletText("[%04d] Window '%s'", window->BeginOrderWithinContext, window->Name);
25575 DebugNodeWindow(window, buf);
25576 Indent();
25577 DebugNodeWindowsListByBeginStackParent(windows + i + 1, windows_size - i - 1, window);
25578 Unindent();
25579 }
25580}
25581
25582//-----------------------------------------------------------------------------
25583// [SECTION] DEBUG LOG WINDOW
25584//-----------------------------------------------------------------------------
25585
25586void ImGui::DebugLog(const char *fmt, ...)
25587{
25588 va_list args;
25589 va_start(args, fmt);
25590 DebugLogV(fmt, args);
25591 va_end(args);
25592}
25593
25594void ImGui::DebugLogV(const char *fmt, va_list args)
25595{
25596 ImGuiContext &g = *GImGui;
25597 const int old_size = g.DebugLogBuf.size();
25598 if (g.ContextName[0] != 0)
25599 g.DebugLogBuf.appendf("[%s] [%05d] ", g.ContextName, g.FrameCount);
25600 else
25601 g.DebugLogBuf.appendf("[%05d] ", g.FrameCount);
25602 g.DebugLogBuf.appendfv(fmt, args);
25603 g.DebugLogIndex.append(g.DebugLogBuf.c_str(), old_size, g.DebugLogBuf.size());
25604 if (g.DebugLogFlags & ImGuiDebugLogFlags_OutputToTTY)
25605 IMGUI_DEBUG_PRINTF("%s", g.DebugLogBuf.begin() + old_size);
25606#ifdef IMGUI_ENABLE_TEST_ENGINE
25607 // IMGUI_TEST_ENGINE_LOG() adds a trailing \n automatically
25608 const int new_size = g.DebugLogBuf.size();
25609 const bool trailing_carriage_return = (g.DebugLogBuf[new_size - 1] == '\n');
25610 if (g.DebugLogFlags & ImGuiDebugLogFlags_OutputToTestEngine)
25611 IMGUI_TEST_ENGINE_LOG("%.*s", new_size - old_size - (trailing_carriage_return ? 1 : 0),
25612 g.DebugLogBuf.begin() + old_size);
25613#endif
25614}
25615
25616// FIXME-LAYOUT: To be done automatically via layout mode once we rework ItemSize/ItemAdd into ItemLayout.
25617static void SameLineOrWrap(const ImVec2 &size)
25618{
25619 ImGuiContext &g = *GImGui;
25620 ImGuiWindow *window = g.CurrentWindow;
25621 ImVec2 pos(window->DC.CursorPosPrevLine.x + g.Style.ItemSpacing.x, window->DC.CursorPosPrevLine.y);
25622 if (window->WorkRect.Contains(ImRect(pos, pos + size)))
25623 ImGui::SameLine();
25624}
25625
25626static void ShowDebugLogFlag(const char *name, ImGuiDebugLogFlags flags)
25627{
25628 ImGuiContext &g = *GImGui;
25629 ImVec2 size(ImGui::GetFrameHeight() + g.Style.ItemInnerSpacing.x + ImGui::CalcTextSize(name).x,
25630 ImGui::GetFrameHeight());
25631 SameLineOrWrap(size); // FIXME-LAYOUT: To be done automatically once we rework ItemSize/ItemAdd into ItemLayout.
25632
25633 bool highlight_errors = (flags == ImGuiDebugLogFlags_EventError && g.DebugLogSkippedErrors > 0);
25634 if (highlight_errors)
25635 ImGui::PushStyleColor(ImGuiCol_Text,
25636 ImLerp(g.Style.Colors[ImGuiCol_Text], ImVec4(1.0f, 0.0f, 0.0f, 1.0f), 0.30f));
25637 if (ImGui::CheckboxFlags(name, &g.DebugLogFlags, flags) && g.IO.KeyShift && (g.DebugLogFlags & flags) != 0)
25638 {
25639 g.DebugLogAutoDisableFrames = 2;
25640 g.DebugLogAutoDisableFlags |= flags;
25641 }
25642 if (highlight_errors)
25643 {
25644 ImGui::PopStyleColor();
25645 ImGui::SetItemTooltip("%d past errors skipped.", g.DebugLogSkippedErrors);
25646 }
25647 else
25648 {
25649 ImGui::SetItemTooltip("Hold SHIFT when clicking to enable for 2 frames only (useful for spammy log entries)");
25650 }
25651}
25652
25653void ImGui::ShowDebugLogWindow(bool *p_open)
25654{
25655 ImGuiContext &g = *GImGui;
25656 if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSize) == 0)
25657 SetNextWindowSize(ImVec2(0.0f, GetFontSize() * 12.0f), ImGuiCond_FirstUseEver);
25658 if (!Begin("Dear ImGui Debug Log", p_open) || GetCurrentWindow()->BeginCount > 1)
25659 {
25660 End();
25661 return;
25662 }
25663
25664 ImGuiDebugLogFlags all_enable_flags = ImGuiDebugLogFlags_EventMask_ & ~ImGuiDebugLogFlags_EventInputRouting;
25665 CheckboxFlags("All", &g.DebugLogFlags, all_enable_flags);
25666 SetItemTooltip("(except InputRouting which is spammy)");
25667
25668 ShowDebugLogFlag("Errors", ImGuiDebugLogFlags_EventError);
25669 ShowDebugLogFlag("ActiveId", ImGuiDebugLogFlags_EventActiveId);
25670 ShowDebugLogFlag("Clipper", ImGuiDebugLogFlags_EventClipper);
25671 ShowDebugLogFlag("Docking", ImGuiDebugLogFlags_EventDocking);
25672 ShowDebugLogFlag("Focus", ImGuiDebugLogFlags_EventFocus);
25673 ShowDebugLogFlag("IO", ImGuiDebugLogFlags_EventIO);
25674 // ShowDebugLogFlag("Font", ImGuiDebugLogFlags_EventFont);
25675 ShowDebugLogFlag("Nav", ImGuiDebugLogFlags_EventNav);
25676 ShowDebugLogFlag("Popup", ImGuiDebugLogFlags_EventPopup);
25677 ShowDebugLogFlag("Selection", ImGuiDebugLogFlags_EventSelection);
25678 ShowDebugLogFlag("Viewport", ImGuiDebugLogFlags_EventViewport);
25679 ShowDebugLogFlag("InputRouting", ImGuiDebugLogFlags_EventInputRouting);
25680
25681 if (SmallButton("Clear"))
25682 {
25683 g.DebugLogBuf.clear();
25684 g.DebugLogIndex.clear();
25685 g.DebugLogSkippedErrors = 0;
25686 }
25687 SameLine();
25688 if (SmallButton("Copy"))
25689 SetClipboardText(g.DebugLogBuf.c_str());
25690 SameLine();
25691 if (SmallButton("Configure Outputs.."))
25692 OpenPopup("Outputs");
25693 if (BeginPopup("Outputs"))
25694 {
25695 CheckboxFlags("OutputToTTY", &g.DebugLogFlags, ImGuiDebugLogFlags_OutputToTTY);
25696#ifndef IMGUI_ENABLE_TEST_ENGINE
25697 BeginDisabled();
25698#endif
25699 CheckboxFlags("OutputToTestEngine", &g.DebugLogFlags, ImGuiDebugLogFlags_OutputToTestEngine);
25700#ifndef IMGUI_ENABLE_TEST_ENGINE
25701 EndDisabled();
25702#endif
25703 EndPopup();
25704 }
25705
25706 BeginChild("##log", ImVec2(0.0f, 0.0f), ImGuiChildFlags_Borders,
25707 ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar);
25708
25709 const ImGuiDebugLogFlags backup_log_flags = g.DebugLogFlags;
25710 g.DebugLogFlags &= ~ImGuiDebugLogFlags_EventClipper;
25711
25712 ImGuiListClipper clipper;
25713 clipper.Begin(g.DebugLogIndex.size());
25714 while (clipper.Step())
25715 for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++)
25716 DebugTextUnformattedWithLocateItem(g.DebugLogIndex.get_line_begin(g.DebugLogBuf.c_str(), line_no),
25717 g.DebugLogIndex.get_line_end(g.DebugLogBuf.c_str(), line_no));
25718 g.DebugLogFlags = backup_log_flags;
25719 if (GetScrollY() >= GetScrollMaxY())
25720 SetScrollHereY(1.0f);
25721 EndChild();
25722
25723 End();
25724}
25725
25726// Display line, search for 0xXXXXXXXX identifiers and call DebugLocateItemOnHover() when hovered.
25727void ImGui::DebugTextUnformattedWithLocateItem(const char *line_begin, const char *line_end)
25728{
25729 TextUnformatted(line_begin, line_end);
25730 if (!IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
25731 return;
25732 ImGuiContext &g = *GImGui;
25733 ImRect text_rect = g.LastItemData.Rect;
25734 for (const char *p = line_begin; p <= line_end - 10; p++)
25735 {
25736 ImGuiID id = 0;
25737 if (p[0] != '0' || (p[1] != 'x' && p[1] != 'X') || sscanf(p + 2, "%X", &id) != 1 || ImCharIsXdigitA(p[10]))
25738 continue;
25739 ImVec2 p0 = CalcTextSize(line_begin, p);
25740 ImVec2 p1 = CalcTextSize(p, p + 10);
25741 g.LastItemData.Rect = ImRect(text_rect.Min + ImVec2(p0.x, 0.0f), text_rect.Min + ImVec2(p0.x + p1.x, p1.y));
25742 if (IsMouseHoveringRect(g.LastItemData.Rect.Min, g.LastItemData.Rect.Max, true))
25743 DebugLocateItemOnHover(id);
25744 p += 10;
25745 }
25746}
25747
25748//-----------------------------------------------------------------------------
25749// [SECTION] OTHER DEBUG TOOLS (ITEM PICKER, ID STACK TOOL)
25750//-----------------------------------------------------------------------------
25751
25752// Draw a small cross at current CursorPos in current window's DrawList
25753void ImGui::DebugDrawCursorPos(ImU32 col)
25754{
25755 ImGuiContext &g = *GImGui;
25756 ImGuiWindow *window = g.CurrentWindow;
25757 ImVec2 pos = window->DC.CursorPos;
25758 window->DrawList->AddLine(ImVec2(pos.x, pos.y - 3.0f), ImVec2(pos.x, pos.y + 4.0f), col, 1.0f);
25759 window->DrawList->AddLine(ImVec2(pos.x - 3.0f, pos.y), ImVec2(pos.x + 4.0f, pos.y), col, 1.0f);
25760}
25761
25762// Draw a 10px wide rectangle around CurposPos.x using Line Y1/Y2 in current window's DrawList
25763void ImGui::DebugDrawLineExtents(ImU32 col)
25764{
25765 ImGuiContext &g = *GImGui;
25766 ImGuiWindow *window = g.CurrentWindow;
25767 float curr_x = window->DC.CursorPos.x;
25768 float line_y1 = (window->DC.IsSameLine ? window->DC.CursorPosPrevLine.y : window->DC.CursorPos.y);
25769 float line_y2 = line_y1 + (window->DC.IsSameLine ? window->DC.PrevLineSize.y : window->DC.CurrLineSize.y);
25770 window->DrawList->AddLine(ImVec2(curr_x - 5.0f, line_y1), ImVec2(curr_x + 5.0f, line_y1), col, 1.0f);
25771 window->DrawList->AddLine(ImVec2(curr_x - 0.5f, line_y1), ImVec2(curr_x - 0.5f, line_y2), col, 1.0f);
25772 window->DrawList->AddLine(ImVec2(curr_x - 5.0f, line_y2), ImVec2(curr_x + 5.0f, line_y2), col, 1.0f);
25773}
25774
25775// Draw last item rect in ForegroundDrawList (so it is always visible)
25776void ImGui::DebugDrawItemRect(ImU32 col)
25777{
25778 ImGuiContext &g = *GImGui;
25779 ImGuiWindow *window = g.CurrentWindow;
25780 GetForegroundDrawList(window)->AddRect(g.LastItemData.Rect.Min, g.LastItemData.Rect.Max, col);
25781}
25782
25783// [DEBUG] Locate item position/rectangle given an ID.
25784static const ImU32 DEBUG_LOCATE_ITEM_COLOR = IM_COL32(0, 255, 0, 255); // Green
25785
25786void ImGui::DebugLocateItem(ImGuiID target_id)
25787{
25788 ImGuiContext &g = *GImGui;
25789 g.DebugLocateId = target_id;
25790 g.DebugLocateFrames = 2;
25791 g.DebugBreakInLocateId = false;
25792}
25793
25794// FIXME: Doesn't work over through a modal window, because they clear HoveredWindow.
25795void ImGui::DebugLocateItemOnHover(ImGuiID target_id)
25796{
25797 if (target_id == 0 ||
25798 !IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_AllowWhenBlockedByPopup))
25799 return;
25800 ImGuiContext &g = *GImGui;
25801 DebugLocateItem(target_id);
25802 GetForegroundDrawList(g.CurrentWindow)
25803 ->AddRect(g.LastItemData.Rect.Min - ImVec2(3.0f, 3.0f), g.LastItemData.Rect.Max + ImVec2(3.0f, 3.0f),
25804 DEBUG_LOCATE_ITEM_COLOR);
25805
25806 // Can't easily use a context menu here because it will mess with focus, active id etc.
25807 if (g.IO.ConfigDebugIsDebuggerPresent && g.MouseStationaryTimer > 1.0f)
25808 {
25809 DebugBreakButtonTooltip(false, "in ItemAdd()");
25810 if (IsKeyChordPressed(g.DebugBreakKeyChord))
25811 g.DebugBreakInLocateId = true;
25812 }
25813}
25814
25815void ImGui::DebugLocateItemResolveWithLastItem()
25816{
25817 ImGuiContext &g = *GImGui;
25818
25819 // [DEBUG] Debug break requested by user
25820 if (g.DebugBreakInLocateId)
25821 IM_DEBUG_BREAK();
25822
25823 ImGuiLastItemData item_data = g.LastItemData;
25824 g.DebugLocateId = 0;
25825 ImDrawList *draw_list = GetForegroundDrawList(g.CurrentWindow);
25826 ImRect r = item_data.Rect;
25827 r.Expand(3.0f);
25828 ImVec2 p1 = g.IO.MousePos;
25829 ImVec2 p2 = ImVec2((p1.x < r.Min.x) ? r.Min.x
25830 : (p1.x > r.Max.x) ? r.Max.x
25831 : p1.x,
25832 (p1.y < r.Min.y) ? r.Min.y
25833 : (p1.y > r.Max.y) ? r.Max.y
25834 : p1.y);
25835 draw_list->AddRect(r.Min, r.Max, DEBUG_LOCATE_ITEM_COLOR);
25836 draw_list->AddLine(p1, p2, DEBUG_LOCATE_ITEM_COLOR);
25837}
25838
25839void ImGui::DebugStartItemPicker()
25840{
25841 ImGuiContext &g = *GImGui;
25842 g.DebugItemPickerActive = true;
25843}
25844
25845// [DEBUG] Item picker tool - start with DebugStartItemPicker() - useful to visually select an item and break into its
25846// call-stack.
25847void ImGui::UpdateDebugToolItemPicker()
25848{
25849 ImGuiContext &g = *GImGui;
25850 g.DebugItemPickerBreakId = 0;
25851 if (!g.DebugItemPickerActive)
25852 return;
25853
25854 const ImGuiID hovered_id = g.HoveredIdPreviousFrame;
25855 SetMouseCursor(ImGuiMouseCursor_Hand);
25856 if (IsKeyPressed(ImGuiKey_Escape))
25857 g.DebugItemPickerActive = false;
25858 const bool change_mapping = g.IO.KeyMods == (ImGuiMod_Ctrl | ImGuiMod_Shift);
25859 if (!change_mapping && IsMouseClicked(g.DebugItemPickerMouseButton) && hovered_id)
25860 {
25861 g.DebugItemPickerBreakId = hovered_id;
25862 g.DebugItemPickerActive = false;
25863 }
25864 for (int mouse_button = 0; mouse_button < 3; mouse_button++)
25865 if (change_mapping && IsMouseClicked(mouse_button))
25866 g.DebugItemPickerMouseButton = (ImU8)mouse_button;
25867 SetNextWindowBgAlpha(0.70f);
25868 if (!BeginTooltip())
25869 return;
25870 Text("HoveredId: 0x%08X", hovered_id);
25871 Text("Press ESC to abort picking.");
25872 const char *mouse_button_names[] = {"Left", "Right", "Middle"};
25873 if (change_mapping)
25874 Text("Remap w/ Ctrl+Shift: click anywhere to select new mouse button.");
25875 else
25876 TextColored(GetStyleColorVec4(hovered_id ? ImGuiCol_Text : ImGuiCol_TextDisabled),
25877 "Click %s Button to break in debugger! (remap w/ Ctrl+Shift)",
25878 mouse_button_names[g.DebugItemPickerMouseButton]);
25879 EndTooltip();
25880}
25881
25882// [DEBUG] ID Stack Tool: update queries. Called by NewFrame()
25883void ImGui::UpdateDebugToolStackQueries()
25884{
25885 ImGuiContext &g = *GImGui;
25886 ImGuiIDStackTool *tool = &g.DebugIDStackTool;
25887
25888 // Clear hook when id stack tool is not visible
25889 g.DebugHookIdInfo = 0;
25890 if (g.FrameCount != tool->LastActiveFrame + 1)
25891 return;
25892
25893 // Update queries. The steps are: -1: query Stack, >= 0: query each stack item
25894 // We can only perform 1 ID Info query every frame. This is designed so the GetID() tests are cheap and
25895 // constant-time
25896 const ImGuiID query_id = g.HoveredIdPreviousFrame ? g.HoveredIdPreviousFrame : g.ActiveId;
25897 if (tool->QueryId != query_id)
25898 {
25899 tool->QueryId = query_id;
25900 tool->StackLevel = -1;
25901 tool->Results.resize(0);
25902 }
25903 if (query_id == 0)
25904 return;
25905
25906 // Advance to next stack level when we got our result, or after 2 frames (in case we never get a result)
25907 int stack_level = tool->StackLevel;
25908 if (stack_level >= 0 && stack_level < tool->Results.Size)
25909 if (tool->Results[stack_level].QuerySuccess || tool->Results[stack_level].QueryFrameCount > 2)
25910 tool->StackLevel++;
25911
25912 // Update hook
25913 stack_level = tool->StackLevel;
25914 if (stack_level == -1)
25915 g.DebugHookIdInfo = query_id;
25916 if (stack_level >= 0 && stack_level < tool->Results.Size)
25917 {
25918 g.DebugHookIdInfo = tool->Results[stack_level].ID;
25919 tool->Results[stack_level].QueryFrameCount++;
25920 }
25921}
25922
25923// [DEBUG] ID Stack tool: hooks called by GetID() family functions
25924void ImGui::DebugHookIdInfo(ImGuiID id, ImGuiDataType data_type, const void *data_id, const void *data_id_end)
25925{
25926 ImGuiContext &g = *GImGui;
25927 ImGuiWindow *window = g.CurrentWindow;
25928 ImGuiIDStackTool *tool = &g.DebugIDStackTool;
25929
25930 // Step 0: stack query
25931 // This assumes that the ID was computed with the current ID stack, which tends to be the case for our widget.
25932 if (tool->StackLevel == -1)
25933 {
25934 tool->StackLevel++;
25935 tool->Results.resize(window->IDStack.Size + 1, ImGuiStackLevelInfo());
25936 for (int n = 0; n < window->IDStack.Size + 1; n++)
25937 tool->Results[n].ID = (n < window->IDStack.Size) ? window->IDStack[n] : id;
25938 return;
25939 }
25940
25941 // Step 1+: query for individual level
25942 IM_ASSERT(tool->StackLevel >= 0);
25943 if (tool->StackLevel != window->IDStack.Size)
25944 return;
25945 ImGuiStackLevelInfo *info = &tool->Results[tool->StackLevel];
25946 IM_ASSERT(info->ID == id && info->QueryFrameCount > 0);
25947
25948 switch (data_type)
25949 {
25950 case ImGuiDataType_S32:
25951 ImFormatString(info->Desc, IM_ARRAYSIZE(info->Desc), "%d", (int)(intptr_t)data_id);
25952 break;
25953 case ImGuiDataType_String:
25954 ImFormatString(info->Desc, IM_ARRAYSIZE(info->Desc), "%.*s",
25955 data_id_end ? (int)((const char *)data_id_end - (const char *)data_id)
25956 : (int)ImStrlen((const char *)data_id),
25957 (const char *)data_id);
25958 break;
25959 case ImGuiDataType_Pointer:
25960 ImFormatString(info->Desc, IM_ARRAYSIZE(info->Desc), "(void*)0x%p", data_id);
25961 break;
25962 case ImGuiDataType_ID:
25963 if (info->Desc[0] != 0) // PushOverrideID() is often used to avoid hashing twice, which would lead to 2 calls to
25964 // DebugHookIdInfo(). We prioritize the first one.
25965 return;
25966 ImFormatString(info->Desc, IM_ARRAYSIZE(info->Desc), "0x%08X [override]", id);
25967 break;
25968 default:
25969 IM_ASSERT(0);
25970 }
25971 info->QuerySuccess = true;
25972 info->DataType = data_type;
25973}
25974
25975static int StackToolFormatLevelInfo(ImGuiIDStackTool *tool, int n, bool format_for_ui, char *buf, size_t buf_size)
25976{
25977 ImGuiStackLevelInfo *info = &tool->Results[n];
25978 ImGuiWindow *window = (info->Desc[0] == 0 && n == 0) ? ImGui::FindWindowByID(info->ID) : NULL;
25979 if (window) // Source: window name (because the root ID don't call GetID() and so doesn't get hooked)
25980 return ImFormatString(buf, buf_size, format_for_ui ? "\"%s\" [window]" : "%s", window->Name);
25981 if (info->QuerySuccess) // Source: GetID() hooks (prioritize over ItemInfo() because we frequently use patterns
25982 // like: PushID(str), Button("") where they both have same id)
25983 return ImFormatString(buf, buf_size,
25984 (format_for_ui && info->DataType == ImGuiDataType_String) ? "\"%s\"" : "%s", info->Desc);
25985 if (tool->StackLevel < tool->Results.Size) // Only start using fallback below when all queries are done, so during
25986 // queries we don't flickering ??? markers.
25987 return (*buf = 0);
25988#ifdef IMGUI_ENABLE_TEST_ENGINE
25989 if (const char *label =
25990 ImGuiTestEngine_FindItemDebugLabel(GImGui, info->ID)) // Source: ImGuiTestEngine's ItemInfo()
25991 return ImFormatString(buf, buf_size, format_for_ui ? "??? \"%s\"" : "%s", label);
25992#endif
25993 return ImFormatString(buf, buf_size, "???");
25994}
25995
25996// ID Stack Tool: Display UI
25997void ImGui::ShowIDStackToolWindow(bool *p_open)
25998{
25999 ImGuiContext &g = *GImGui;
26000 if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSize) == 0)
26001 SetNextWindowSize(ImVec2(0.0f, GetFontSize() * 8.0f), ImGuiCond_FirstUseEver);
26002 if (!Begin("Dear ImGui ID Stack Tool", p_open) || GetCurrentWindow()->BeginCount > 1)
26003 {
26004 End();
26005 return;
26006 }
26007
26008 // Display hovered/active status
26009 ImGuiIDStackTool *tool = &g.DebugIDStackTool;
26010
26011 // Build and display path
26012 tool->ResultPathBuf.resize(0);
26013 for (int stack_n = 0; stack_n < tool->Results.Size; stack_n++)
26014 {
26015 char level_desc[256];
26016 StackToolFormatLevelInfo(tool, stack_n, false, level_desc, IM_ARRAYSIZE(level_desc));
26017 tool->ResultPathBuf.append(stack_n == 0 ? "//" : "/");
26018 for (int n = 0; level_desc[n]; n++)
26019 {
26020 if (level_desc[n] == '/')
26021 tool->ResultPathBuf.append("\\");
26022 tool->ResultPathBuf.append(level_desc + n, level_desc + n + 1);
26023 }
26024 }
26025 Text("0x%08X", tool->QueryId);
26026 SameLine();
26027 MetricsHelpMarker(
26028 "Hover an item with the mouse to display elements of the ID Stack leading to the item's final ID.\nEach level "
26029 "of the stack correspond to a PushID() call.\nAll levels of the stack are hashed together to make the final ID "
26030 "of a widget (ID displayed at the bottom level of the stack).\nRead FAQ entry about the ID stack for details.");
26031
26032 // CTRL+C to copy path
26033 const float time_since_copy = (float)g.Time - tool->CopyToClipboardLastTime;
26034 SameLine();
26035 PushStyleVarY(ImGuiStyleVar_FramePadding, 0.0f);
26036 Checkbox("Ctrl+C: copy path", &tool->CopyToClipboardOnCtrlC);
26037 PopStyleVar();
26038 SameLine();
26039 TextColored((time_since_copy >= 0.0f && time_since_copy < 0.75f && ImFmod(time_since_copy, 0.25f) < 0.25f * 0.5f)
26040 ? ImVec4(1.f, 1.f, 0.3f, 1.f)
26041 : ImVec4(),
26042 "*COPIED*");
26043 if (tool->CopyToClipboardOnCtrlC &&
26044 Shortcut(ImGuiMod_Ctrl | ImGuiKey_C, ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteOverFocused))
26045 {
26046 tool->CopyToClipboardLastTime = (float)g.Time;
26047 SetClipboardText(tool->ResultPathBuf.c_str());
26048 }
26049
26050 Text("- Path \"%s\"", tool->ResultPathBuf.c_str());
26051#ifdef IMGUI_ENABLE_TEST_ENGINE
26052 Text("- Label \"%s\"", tool->QueryId ? ImGuiTestEngine_FindItemDebugLabel(&g, tool->QueryId) : "");
26053#endif
26054
26055 Separator();
26056
26057 // Display decorated stack
26058 tool->LastActiveFrame = g.FrameCount;
26059 if (tool->Results.Size > 0 && BeginTable("##table", 3, ImGuiTableFlags_Borders))
26060 {
26061 const float id_width = CalcTextSize("0xDDDDDDDD").x;
26062 TableSetupColumn("Seed", ImGuiTableColumnFlags_WidthFixed, id_width);
26063 TableSetupColumn("PushID", ImGuiTableColumnFlags_WidthStretch);
26064 TableSetupColumn("Result", ImGuiTableColumnFlags_WidthFixed, id_width);
26065 TableHeadersRow();
26066 for (int n = 0; n < tool->Results.Size; n++)
26067 {
26068 ImGuiStackLevelInfo *info = &tool->Results[n];
26069 TableNextColumn();
26070 Text("0x%08X", (n > 0) ? tool->Results[n - 1].ID : 0);
26071 TableNextColumn();
26072 StackToolFormatLevelInfo(tool, n, true, g.TempBuffer.Data, g.TempBuffer.Size);
26073 TextUnformatted(g.TempBuffer.Data);
26074 TableNextColumn();
26075 Text("0x%08X", info->ID);
26076 if (n == tool->Results.Size - 1)
26077 TableSetBgColor(ImGuiTableBgTarget_CellBg, GetColorU32(ImGuiCol_Header));
26078 }
26079 EndTable();
26080 }
26081 End();
26082}
26083
26084#else
26085
26086void ImGui::ShowMetricsWindow(bool *)
26087{
26088}
26089void ImGui::ShowFontAtlas(ImFontAtlas *)
26090{
26091}
26092void ImGui::DebugNodeColumns(ImGuiOldColumns *)
26093{
26094}
26095void ImGui::DebugNodeDrawList(ImGuiWindow *, ImGuiViewportP *, const ImDrawList *, const char *)
26096{
26097}
26098void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList *, const ImDrawList *, const ImDrawCmd *, bool, bool)
26099{
26100}
26101void ImGui::DebugNodeFont(ImFont *)
26102{
26103}
26104void ImGui::DebugNodeStorage(ImGuiStorage *, const char *)
26105{
26106}
26107void ImGui::DebugNodeTabBar(ImGuiTabBar *, const char *)
26108{
26109}
26110void ImGui::DebugNodeWindow(ImGuiWindow *, const char *)
26111{
26112}
26113void ImGui::DebugNodeWindowSettings(ImGuiWindowSettings *)
26114{
26115}
26116void ImGui::DebugNodeWindowsList(ImVector<ImGuiWindow *> *, const char *)
26117{
26118}
26119void ImGui::DebugNodeViewport(ImGuiViewportP *)
26120{
26121}
26122
26123void ImGui::ShowDebugLogWindow(bool *)
26124{
26125}
26126void ImGui::ShowIDStackToolWindow(bool *)
26127{
26128}
26129void ImGui::DebugStartItemPicker()
26130{
26131}
26132void ImGui::DebugHookIdInfo(ImGuiID, ImGuiDataType, const void *, const void *)
26133{
26134}
26135
26136#endif // #ifndef IMGUI_DISABLE_DEBUG_TOOLS
26137
26138//-----------------------------------------------------------------------------
26139
26140// Include imgui_user.inl at the end of imgui.cpp to access private data/functions that aren't exposed.
26141// Prefer just including imgui_internal.h from your code rather than using this define. If a declaration is missing from
26142// imgui_internal.h add it or request it on the github.
26143#ifdef IMGUI_INCLUDE_IMGUI_USER_INL
26144#include "imgui_user.inl"
26145#endif
26146
26147//-----------------------------------------------------------------------------
26148
26149#endif // #ifndef IMGUI_DISABLE
int IsKeyReleased(DN_Keycode code)
Checks if key was released this frame.
Definition Input.cpp:111
int IsKeyDown(DN_Keycode code)
Checks if key is currently held down.
Definition Input.cpp:118
int IsKeyPressed(DN_Keycode code)
Checks if key was pressed this frame (not held).
Definition Input.cpp:97
void SetMouseCursor(int cursor)
Sets the mouse cursor style.
Definition Input.cpp:264